Manually merge changes from giant AOSP topic

Change-Id: Ie706753a6894f5175b2d00f376ab1966ed9eb4e3
diff --git a/Android.mk b/Android.mk
index d41e184..0dc7ff1 100644
--- a/Android.mk
+++ b/Android.mk
@@ -17,6 +17,14 @@
 LOCAL_PATH := $(call my-dir)
 
 #
+# Prebuilt Java Libraries
+#
+include $(CLEAR_VARS)
+LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES := \
+    libSharedSystemUI:quickstep/libs/sysui_shared.jar
+include $(BUILD_MULTI_PREBUILT)
+
+#
 # Build rule for Launcher3 app.
 #
 include $(CLEAR_VARS)
@@ -26,12 +34,11 @@
 LOCAL_STATIC_JAVA_LIBRARIES := \
     android-support-v4 \
     android-support-v7-recyclerview \
-    android-support-v7-palette \
     android-support-dynamic-animation
 
 LOCAL_SRC_FILES := \
     $(call all-java-files-under, src) \
-    $(call all-java-files-under, src_config) \
+    $(call all-java-files-under, src_ui_overrides) \
     $(call all-java-files-under, src_flags) \
     $(call all-proto-files-under, protos) \
     $(call all-proto-files-under, proto_overrides)
@@ -72,12 +79,11 @@
 LOCAL_STATIC_JAVA_LIBRARIES := \
     android-support-v4 \
     android-support-v7-recyclerview \
-    android-support-v7-palette \
     android-support-dynamic-animation
 
 LOCAL_SRC_FILES := \
     $(call all-java-files-under, src) \
-    $(call all-java-files-under, src_config) \
+    $(call all-java-files-under, src_ui_overrides) \
     $(call all-java-files-under, go/src_flags) \
     $(call all-proto-files-under, protos) \
     $(call all-proto-files-under, proto_overrides)
@@ -114,22 +120,57 @@
 include $(BUILD_PACKAGE)
 
 #
-# Launcher proto buffer jar used for development
+# Build rule for Quickstep app.
 #
 include $(CLEAR_VARS)
 
-LOCAL_SRC_FILES := $(call all-proto-files-under, protos) $(call all-proto-files-under, proto_overrides)
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-v4 \
+    android-support-v7-recyclerview \
+    android-support-dynamic-animation \
+    libSharedSystemUI
+
+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-proto-files-under, protos) \
+    $(call all-proto-files-under, proto_overrides)
+
+LOCAL_RESOURCE_DIR := \
+    $(LOCAL_PATH)/quickstep/res \
+    $(LOCAL_PATH)/res \
+    prebuilts/sdk/current/support/v7/recyclerview/res \
+
+LOCAL_PROGUARD_ENABLED := disabled
 
 LOCAL_PROTOC_OPTIMIZE_TYPE := nano
 LOCAL_PROTOC_FLAGS := --proto_path=$(LOCAL_PATH)/protos/ --proto_path=$(LOCAL_PATH)/proto_overrides/
 LOCAL_PROTO_JAVA_OUTPUT_PARAMS := enum_style=java
 
-LOCAL_MODULE_TAGS := optional
-LOCAL_MODULE := launcher_proto_lib
-LOCAL_IS_HOST_MODULE := true
-LOCAL_STATIC_JAVA_LIBRARIES := host-libprotobuf-java-nano
+LOCAL_AAPT_FLAGS := \
+    --auto-add-overlay \
+    --extra-packages android.support.v7.recyclerview \
 
-include $(BUILD_HOST_JAVA_LIBRARY)
+LOCAL_SDK_VERSION := system_current
+LOCAL_MIN_SDK_VERSION := 26
+LOCAL_PACKAGE_NAME := Launcher3QuickStep
+LOCAL_PRIVILEGED_MODULE := true
+LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3
+
+LOCAL_FULL_LIBS_MANIFEST_FILES := \
+    $(LOCAL_PATH)/AndroidManifest.xml \
+    $(LOCAL_PATH)/AndroidManifest-common.xml
+
+LOCAL_MANIFEST_FILE := quickstep/AndroidManifest.xml
+LOCAL_JACK_COVERAGE_INCLUDE_FILTER := com.android.launcher3.*
+
+include $(BUILD_PACKAGE)
+
+
+
 
 # ==================================================
 include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index ad404c0..bb03f50 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -43,6 +43,7 @@
     <uses-permission android:name="android.permission.BIND_APPWIDGET" />
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+    <uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
 
     <application
         android:backupAgent="com.android.launcher3.LauncherBackupAgent"
@@ -80,12 +81,6 @@
             </intent-filter>
         </receiver>
 
-        <service android:name="com.android.launcher3.dynamicui.ColorExtractionService"
-            android:exported="false"
-            android:process=":wallpaper_chooser"
-            android:permission="android.permission.BIND_JOB_SERVICE">
-        </service>
-
         <service
             android:name="com.android.launcher3.compat.WallpaperManagerCompatVL$ColorExtractionService"
             android:exported="false"
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 23bddf6..6ef7828 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -112,53 +112,5 @@
             android:writePermission="com.android.launcher3.permission.WRITE_SETTINGS"
             android:readPermission="com.android.launcher3.permission.READ_SETTINGS" />
 
-        <!-- ENABLE_FOR_TESTING
-
-        <activity
-            android:name="com.android.launcher3.testing.LauncherExtension"
-            android:launchMode="singleTask"
-            android:clearTaskOnLaunch="true"
-            android:stateNotNeeded="true"
-            android:theme="@style/Theme"
-            android:windowSoftInputMode="adjustPan"
-            android:screenOrientation="nosensor"
-            >
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.HOME" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.MONKEY"/>
-            </intent-filter>
-        </activity>
-
-        <activity
-            android:name="com.android.launcher3.testing.MemoryDumpActivity"
-            android:theme="@android:style/Theme.NoDisplay"
-            android:label="* HPROF"
-            android:excludeFromRecents="true"
-            android:icon="@drawable/ic_launcher_home"
-            >
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.LAUNCHER" />
-            </intent-filter>
-        </activity>
-
-        <activity
-            android:name="com.android.launcher3.testing.ToggleWeightWatcher"
-            android:label="Show Mem"
-            android:icon="@drawable/ic_launcher_home">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.LAUNCHER" />
-            </intent-filter>
-        </activity>
-
-        <service android:name="com.android.launcher3.testing.MemoryTracker" />
-
-        -->
-
     </application>
 </manifest>
diff --git a/build.gradle b/build.gradle
index 886ccac..61c05e5 100644
--- a/build.gradle
+++ b/build.gradle
@@ -4,7 +4,7 @@
         jcenter()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:2.3.1'
+        classpath 'com.android.tools.build:gradle:2.3.3'
         classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.0'
     }
 }
@@ -13,12 +13,12 @@
 apply plugin: 'com.google.protobuf'
 
 android {
-    compileSdkVersion 26
-    buildToolsVersion '26.0.0'
+    compileSdkVersion 27
+    buildToolsVersion '27.0.0'
 
     defaultConfig {
         minSdkVersion 21
-        targetSdkVersion 26
+        targetSdkVersion 27
         versionCode 1
         versionName "1.0"
 
@@ -40,7 +40,20 @@
             applicationId 'com.android.launcher3'
             testApplicationId 'com.android.launcher3.tests'
         }
+
+        quickstep {
+            applicationId 'com.android.launcher3'
+            testApplicationId 'com.android.launcher3.tests'
+        }
     }
+
+    // Disable release builds for now
+    android.variantFilter { variant ->
+        if (variant.buildType.name.endsWith('release')) {
+            variant.setIgnore(true);
+        }
+    }
+
     sourceSets {
         main {
             res.srcDirs = ['res']
@@ -52,31 +65,34 @@
             }
         }
 
+        debug {
+            manifest.srcFile "AndroidManifest.xml"
+        }
+
         androidTest {
             res.srcDirs = ['tests/res']
             java.srcDirs = ['tests/src']
             manifest.srcFile "tests/AndroidManifest-common.xml"
         }
 
-        aosp {
-            java.srcDirs = ['src_flags']
-            manifest.srcFile "AndroidManifest.xml"
+        androidTestDebug {
+            manifest.srcFile "tests/AndroidManifest.xml"
         }
 
-        aospAndroidTest {
-            manifest.srcFile "tests/AndroidManifest.xml"
+        aosp {
+            java.srcDirs = ['src_flags', "src_ui_overrides"]
         }
 
         l3go {
             res.srcDirs = ['go/res']
-            java.srcDirs = ['go/src_flags']
-            // Note: we are using the Launcher3 manifest here because the gradle manifest-merger uses
-            // different attributes than the build system.
-            manifest.srcFile "AndroidManifest.xml"
+            java.srcDirs = ['go/src_flags', "src_ui_overrides"]
+            manifest.srcFile "go/AndroidManifest.xml"
         }
 
-        l3goAndroidTest {
-            manifest.srcFile "tests/AndroidManifest.xml"
+        quickstep {
+            res.srcDirs = ['quickstep/res']
+            java.srcDirs = ['src_flags', 'quickstep/src']
+            manifest.srcFile "quickstep/AndroidManifest.xml"
         }
     }
 }
@@ -86,19 +102,21 @@
     jcenter()
 }
 
-final String SUPPORT_LIBS_VERSION = '26.0.0-SNAPSHOT'
+final String SUPPORT_LIBS_VERSION = '27.0.0-SNAPSHOT'
 dependencies {
     compile "com.android.support:support-v4:${SUPPORT_LIBS_VERSION}"
     compile "com.android.support:support-dynamic-animation:${SUPPORT_LIBS_VERSION}"
     compile "com.android.support:recyclerview-v7:${SUPPORT_LIBS_VERSION}"
-    compile "com.android.support:palette-v7:${SUPPORT_LIBS_VERSION}"
     compile 'com.google.protobuf.nano:protobuf-javanano:3.0.0-alpha-7'
 
+    quickstepCompile fileTree(dir: "quickstep/libs", include: 'sysui_shared.jar')
+
     testCompile 'junit:junit:4.12'
     androidTestCompile "org.mockito:mockito-core:1.9.5"
     androidTestCompile 'com.google.dexmaker:dexmaker:1.2'
     androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2'
-    androidTestCompile 'com.android.support.test:runner:0.5'
+    androidTestCompile 'com.android.support.test:runner:1.0.0'
+    androidTestCompile 'com.android.support.test:rules:1.0.0'
     androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.2'
     androidTestCompile "com.android.support:support-annotations:${SUPPORT_LIBS_VERSION}"
 }
diff --git a/go/res/values-az-rAZ/strings.xml b/go/res/values-az-rAZ/strings.xml
deleted file mode 100644
index c4b8cb7..0000000
--- a/go/res/values-az-rAZ/strings.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?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.
-*/
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="long_press_widget_to_add" msgid="4001616142797446267">"Qısayolu seçmək üçün toxunub saxlayın."</string>
-    <string name="long_accessible_way_to_add" msgid="2725225828389948328">"Qısayolu seçmək üçün iki dəfə basıb saxlayın və ya fərdi əməliyyatlardan istifadə edin."</string>
-    <string name="widget_button_text" msgid="4221900832360456858">"Qısayollar"</string>
-    <string name="widgets_bottom_sheet_title" msgid="3949835990909395998">"<xliff:g id="NAME">%1$s</xliff:g> qısayolları"</string>
-</resources>
diff --git a/go/res/values-bs-rBA/strings.xml b/go/res/values-bs-rBA/strings.xml
deleted file mode 100644
index 7042468..0000000
--- a/go/res/values-bs-rBA/strings.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?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.
-*/
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="long_press_widget_to_add" msgid="4001616142797446267">"Dodirnite i držite da uzmete prečicu."</string>
-    <string name="long_accessible_way_to_add" msgid="2725225828389948328">"Dvaput dodirnite i držite da uzmete prečicu ili koristite prilagođene akcije."</string>
-    <string name="widget_button_text" msgid="4221900832360456858">"Prečice"</string>
-    <string name="widgets_bottom_sheet_title" msgid="3949835990909395998">"Prečice aplikacije <xliff:g id="NAME">%1$s</xliff:g>"</string>
-</resources>
diff --git a/go/res/values-hi/strings.xml b/go/res/values-hi/strings.xml
index bc05776..2c1650a 100644
--- a/go/res/values-hi/strings.xml
+++ b/go/res/values-hi/strings.xml
@@ -20,7 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="long_press_widget_to_add" msgid="4001616142797446267">"शॉर्टकट चुनने के लिए छूकर रखें."</string>
-    <string name="long_accessible_way_to_add" msgid="2725225828389948328">"शॉर्टकट चुनने के लिए डबल टैप करके रखें या कस्टम कार्रवाइयों का उपयोग करें."</string>
+    <string name="long_accessible_way_to_add" msgid="2725225828389948328">"शॉर्टकट चुनने के लिए दो बार छूएं और कुछ देर दबाएं रखें या अपने मुताबिक कार्रवाइयों का इस्तेमाल करें."</string>
     <string name="widget_button_text" msgid="4221900832360456858">"शॉर्टकट"</string>
     <string name="widgets_bottom_sheet_title" msgid="3949835990909395998">"<xliff:g id="NAME">%1$s</xliff:g> शॉर्टकट"</string>
 </resources>
diff --git a/go/res/values-ka-rGE/strings.xml b/go/res/values-ka-rGE/strings.xml
deleted file mode 100644
index 1b46534..0000000
--- a/go/res/values-ka-rGE/strings.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?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.
-*/
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="long_press_widget_to_add" msgid="4001616142797446267">"შეეხეთ და დააყოვნეთ მალსახმობის ასარჩევად."</string>
-    <string name="long_accessible_way_to_add" msgid="2725225828389948328">"ორმაგად შეეხეთ და გეჭიროთ მალსახმობის ასარჩევად ან მორგებული მოქმედებების გამოსაყენებლად."</string>
-    <string name="widget_button_text" msgid="4221900832360456858">"მალსახმობები"</string>
-    <string name="widgets_bottom_sheet_title" msgid="3949835990909395998">"<xliff:g id="NAME">%1$s</xliff:g>-ის მალსახმობები"</string>
-</resources>
diff --git a/go/res/values-ky-rKG/strings.xml b/go/res/values-ky-rKG/strings.xml
deleted file mode 100644
index 4c7e973..0000000
--- a/go/res/values-ky-rKG/strings.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?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.
-*/
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="long_press_widget_to_add" msgid="4001616142797446267">"Кыска жолду тандоо үчүн басып туруңуз."</string>
-    <string name="long_accessible_way_to_add" msgid="2725225828389948328">"Кыска жолду тандоо үчүн эки жолу таптап, кармап туруңуз же ыңгайлаштырылган аракеттерди колдонуңуз."</string>
-    <string name="widget_button_text" msgid="4221900832360456858">"Кыска жолдор"</string>
-    <string name="widgets_bottom_sheet_title" msgid="3949835990909395998">"<xliff:g id="NAME">%1$s</xliff:g> кыска жол"</string>
-</resources>
diff --git a/go/res/values-lo-rLA/strings.xml b/go/res/values-lo-rLA/strings.xml
deleted file mode 100644
index 7864884..0000000
--- a/go/res/values-lo-rLA/strings.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?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.
-*/
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="long_press_widget_to_add" msgid="4001616142797446267">"ແຕະຄ້າງໄວ້ເພື່ອຮັບປຸ່ມລັດ."</string>
-    <string name="long_accessible_way_to_add" msgid="2725225828389948328">"ແຕະສອງເທື່ອຄ້າງໄວ້ເພື່ອຮັບປຸ່ມລັດ ຫຼື ໃຊ້ຄຳສັ່ງແບບກຳນົດເອງ."</string>
-    <string name="widget_button_text" msgid="4221900832360456858">"ປຸ່ມລັດ"</string>
-    <string name="widgets_bottom_sheet_title" msgid="3949835990909395998">"ປຸ່ມລັດ <xliff:g id="NAME">%1$s</xliff:g>"</string>
-</resources>
diff --git a/go/res/values-pa/strings.xml b/go/res/values-pa/strings.xml
index f3982ab..c7e4abf 100644
--- a/go/res/values-pa/strings.xml
+++ b/go/res/values-pa/strings.xml
@@ -20,7 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="long_press_widget_to_add" msgid="4001616142797446267">"ਕੋਈ ਸ਼ਾਰਟਕੱਟ ਚੁਣਨ ਲਈ ਸਪੱਰਸ਼ ਕਰੋ ਅਤੇ ਦਬਾ ਕੇ ਰੱਖੋ।"</string>
-    <string name="long_accessible_way_to_add" msgid="2725225828389948328">"ਕੋਈ ਸ਼ਾਰਟਕੱਟ ਚੁਣਨ ਲਈ ਡਬਲ-ਟੈਪ ਕਰੋ ਅਤੇ ਦਬਾ ਕੇ ਰੱਖੋ ਜਾਂ ਵਿਸ਼ੇਸ਼-ਵਿਉਂਤਬੱਧ ਕਾਰਵਾਈਆਂ ਵਰਤੋ।"</string>
+    <string name="long_accessible_way_to_add" msgid="2725225828389948328">"ਕੋਈ ਸ਼ਾਰਟਕੱਟ ਚੁਣਨ ਲਈ ਦੋ ਵਾਰ ਟੈਪ ਕਰੋ ਅਤੇ ਦਬਾ ਕੇ ਰੱਖੋ ਜਾਂ ਵਿਉਂਂਤੀ ਕਾਰਵਾਈਆਂ ਵਰਤੋ।"</string>
     <string name="widget_button_text" msgid="4221900832360456858">"ਸ਼ਾਰਟਕੱਟ"</string>
     <string name="widgets_bottom_sheet_title" msgid="3949835990909395998">"<xliff:g id="NAME">%1$s</xliff:g> ਸ਼ਾਰਟਕੱਟ"</string>
 </resources>
diff --git a/go/res/values-si-rLK/strings.xml b/go/res/values-si-rLK/strings.xml
deleted file mode 100644
index 4b25c90..0000000
--- a/go/res/values-si-rLK/strings.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?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.
-*/
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="long_press_widget_to_add" msgid="4001616142797446267">"කෙටි මගක් තෝරා ගැනීමට ස්පර්ශ කර අල්ලාගෙන සිටින්න."</string>
-    <string name="long_accessible_way_to_add" msgid="2725225828389948328">"විජට් එකක් තෝරා ගැනීමට හෝ අභිරුචි භාවිත කිරීමට දෙවරක් තට්ටු කර අල්ලා ගෙන සිටින්න."</string>
-    <string name="widget_button_text" msgid="4221900832360456858">"කෙටි මං"</string>
-    <string name="widgets_bottom_sheet_title" msgid="3949835990909395998">"කෙටි මං <xliff:g id="NAME">%1$s</xliff:g>"</string>
-</resources>
diff --git a/go/res/values-sw/strings.xml b/go/res/values-sw/strings.xml
index 0379ed0..13c12e4 100644
--- a/go/res/values-sw/strings.xml
+++ b/go/res/values-sw/strings.xml
@@ -20,7 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="long_press_widget_to_add" msgid="4001616142797446267">"Gusa na ushikilie ili uchague njia ya mkato."</string>
-    <string name="long_accessible_way_to_add" msgid="2725225828389948328">"Gonga mara mbili na ushikilie ile uchague njia ya mkato au utumie vitendo maalum."</string>
+    <string name="long_accessible_way_to_add" msgid="2725225828389948328">"Gusa mara mbili na ushikilie ile uchague njia ya mkato au utumie vitendo maalum."</string>
     <string name="widget_button_text" msgid="4221900832360456858">"Njia za mkato"</string>
     <string name="widgets_bottom_sheet_title" msgid="3949835990909395998">"Njia za mkato za <xliff:g id="NAME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/go/res/values-uz-rUZ/strings.xml b/go/res/values-uz-rUZ/strings.xml
deleted file mode 100644
index 318bc15..0000000
--- a/go/res/values-uz-rUZ/strings.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?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.
-*/
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="long_press_widget_to_add" msgid="4001616142797446267">"Yorliqni tanlab olish uchun bosib turing."</string>
-    <string name="long_accessible_way_to_add" msgid="2725225828389948328">"Ikki marta bosib va bosib turgan holatda yorliqni tanlang yoki maxsus amaldan foydalaning."</string>
-    <string name="widget_button_text" msgid="4221900832360456858">"Yorliqlar"</string>
-    <string name="widgets_bottom_sheet_title" msgid="3949835990909395998">"<xliff:g id="NAME">%1$s</xliff:g> ilovasi yorliqlari"</string>
-</resources>
diff --git a/proguard.flags b/proguard.flags
index 51abcca..a315cdc 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -86,13 +86,15 @@
   public <init>(...);
 }
 
+# The support library contains references to newer platform versions.
+# Don't warn about those in case this app is linking against an older
+# platform version.  We know about them, and they are safe.
+-dontwarn android.support.**
+
 # Proguard will strip methods required for talkback to properly scroll to
 # next row when focus is on the last item of last row when using a RecyclerView
 # Keep optimized and shrunk proguard to prevent issues like this when using
 # support jar.
-#-keep,allowoptimization,allowshrinking class android.support.** {
-#  *;
-#}
 -keep class android.support.v7.widget.RecyclerView { *; }
 
 -keep interface com.android.launcher3.userevent.nano.LauncherLogProto.** {
diff --git a/protos/launcher_log.proto b/protos/launcher_log.proto
index 0bbec18..de74fce 100644
--- a/protos/launcher_log.proto
+++ b/protos/launcher_log.proto
@@ -132,7 +132,9 @@
                   // not using the HOME_INTENT
     CANCEL = 3;   // Indicates that a confirmation screen was cancelled
     CONFIRM = 4;  // Indicates thata confirmation screen was accepted
+    STOP = 5;     // Indicates onStop() was called (screen time out, power off)
   }
+
   optional Type type = 1;
   optional Touch touch = 2;
   optional Direction dir = 3;
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
new file mode 100644
index 0000000..08c740c
--- /dev/null
+++ b/quickstep/AndroidManifest.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 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.
+*/
+-->
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.launcher3" >
+
+    <uses-sdk android:targetSdkVersion="23" android:minSdkVersion="21"/>
+
+    <application
+        android:backupAgent="com.android.launcher3.LauncherBackupAgent"
+        android:fullBackupOnly="true"
+        android:fullBackupContent="@xml/backupscheme"
+        android:hardwareAccelerated="true"
+        android:icon="@drawable/ic_launcher_home"
+        android:label="@string/derived_app_name"
+        android:theme="@style/LauncherTheme"
+        android:largeHeap="@bool/config_largeHeap"
+        android:restoreAnyVersion="true"
+        android:supportsRtl="true" >
+
+        <service android:name="com.android.quickstep.TouchInteractionService"
+            android:exported="true" />
+
+        <!-- STOPSHIP: Change exported to false once all the integration is complete.
+        It is set to true so that the activity can be started from command line -->
+        <activity android:name="com.android.quickstep.RecentsActivity"
+            android:exported="true" />
+    </application>
+
+</manifest>
diff --git a/quickstep/libs/sysui_shared.jar b/quickstep/libs/sysui_shared.jar
new file mode 100644
index 0000000..fa0e0f3
--- /dev/null
+++ b/quickstep/libs/sysui_shared.jar
Binary files differ
diff --git a/quickstep/res/drawable/task_thumbnail_background.xml b/quickstep/res/drawable/task_thumbnail_background.xml
new file mode 100644
index 0000000..603380e
--- /dev/null
+++ b/quickstep/res/drawable/task_thumbnail_background.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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
+    <solid android:color="#FF000000" />
+    <corners android:radius="2dp" />
+</shape>
diff --git a/res/layout/zzz_dummy_widget.xml b/quickstep/res/layout/overview_panel.xml
similarity index 61%
rename from res/layout/zzz_dummy_widget.xml
rename to quickstep/res/layout/overview_panel.xml
index a0fa8fc..22f014a 100644
--- a/res/layout/zzz_dummy_widget.xml
+++ b/quickstep/res/layout/overview_panel.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
+<!--
+     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.
@@ -13,23 +14,12 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-
-<LinearLayout
+<com.android.quickstep.RecentsView
     xmlns:android="http://schemas.android.com/apk/res/android"
+    android:theme="@style/HomeScreenElementTheme"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:orientation="horizontal">
-
-    <FrameLayout
-        android:layout_width="0dp"
-        android:layout_height="match_parent"
-        android:layout_weight="1"
-        android:background="#ffff0000" />
-
-    <FrameLayout
-        android:layout_width="0dp"
-        android:layout_height="match_parent"
-        android:layout_weight="1"
-        android:background="#ff00ff00" />
-
-</LinearLayout>
\ No newline at end of file
+    android:clipChildren="false"
+    android:clipToPadding="false"
+    android:alpha="0.0"
+    android:visibility="invisible" />
\ No newline at end of file
diff --git a/quickstep/res/layout/task.xml b/quickstep/res/layout/task.xml
new file mode 100644
index 0000000..839d934
--- /dev/null
+++ b/quickstep/res/layout/task.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.
+-->
+<com.android.quickstep.TaskView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <com.android.quickstep.TaskThumbnailView
+        android:id="@+id/snapshot"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_marginTop="@dimen/task_thumbnail_top_margin"
+        android:scaleType="matrix"
+        android:background="@drawable/task_thumbnail_background"
+        android:elevation="4dp" />
+    <ImageView
+        android:id="@+id/icon"
+        android:layout_width="@dimen/task_thumbnail_icon_size"
+        android:layout_height="@dimen/task_thumbnail_icon_size"
+        android:layout_gravity="top|center_horizontal"
+        android:elevation="5dp"/>
+</com.android.quickstep.TaskView>
\ No newline at end of file
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
new file mode 100644
index 0000000..4f85957
--- /dev/null
+++ b/quickstep/res/values/dimens.xml
@@ -0,0 +1,25 @@
+<?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.
+-->
+
+<resources>
+
+    <dimen name="task_thumbnail_top_margin">24dp</dimen>
+    <dimen name="task_thumbnail_icon_size">48dp</dimen>
+
+    <dimen name="quickstep_fling_threshold_velocity">500dp</dimen>
+    <dimen name="quickstep_fling_min_velocity">250dp</dimen>
+
+</resources>
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
new file mode 100644
index 0000000..ef61226
--- /dev/null
+++ b/quickstep/res/values/strings.xml
@@ -0,0 +1,23 @@
+<?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.
+*/
+-->
+<resources>
+
+    <!-- Application name -->
+    <string name="derived_app_name" translatable="false">Quickstep</string>
+</resources>
\ No newline at end of file
diff --git a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
new file mode 100644
index 0000000..1176034
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
@@ -0,0 +1,60 @@
+/*
+ * 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.uioverrides;
+
+import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
+
+import android.view.View;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.quickstep.RecentsView;
+
+/**
+ * Definition for overview state
+ */
+public class OverviewState extends LauncherState {
+
+    private static final int STATE_FLAGS = FLAG_SHOW_SCRIM | FLAG_MULTI_PAGE;
+
+    public OverviewState(int id) {
+        super(id, ContainerType.WORKSPACE, OVERVIEW_TRANSITION_MS, 1f, STATE_FLAGS);
+    }
+
+    @Override
+    public float[] getWorkspaceScaleAndTranslation(Launcher launcher) {
+        // TODO: Find a better transition
+        return new float[] {0f, 0};
+    }
+
+    @Override
+    public void onStateEnabled(Launcher launcher) {
+        RecentsView rv = launcher.getOverviewPanel();
+        rv.setOverviewStateEnabled(true);
+    }
+
+    @Override
+    public void onStateDisabled(Launcher launcher) {
+        RecentsView rv = launcher.getOverviewPanel();
+        rv.setOverviewStateEnabled(false);
+    }
+
+    @Override
+    public View getFinalFocus(Launcher launcher) {
+        return launcher.getOverviewPanel();
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
new file mode 100644
index 0000000..da1eff9
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -0,0 +1,53 @@
+/*
+ * 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.uioverrides;
+
+import static com.android.launcher3.WorkspaceStateTransitionAnimation.NO_ANIM_PROPERTY_SETTER;
+
+import android.animation.AnimatorSet;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager.AnimationConfig;
+import com.android.launcher3.LauncherStateManager.StateHandler;
+import com.android.launcher3.WorkspaceStateTransitionAnimation.AnimatedPropertySetter;
+import com.android.launcher3.WorkspaceStateTransitionAnimation.PropertySetter;
+import com.android.launcher3.anim.AnimationLayerSet;
+
+public class RecentsViewStateController implements StateHandler {
+
+    private final Launcher mLauncher;
+
+    public RecentsViewStateController(Launcher launcher) {
+        mLauncher = launcher;
+    }
+
+    @Override
+    public void setState(LauncherState state) {
+        setState(state, NO_ANIM_PROPERTY_SETTER);
+    }
+
+    @Override
+    public void setStateWithAnimation(LauncherState toState, AnimationLayerSet layerViews,
+            AnimatorSet anim, AnimationConfig config) {
+        setState(toState, new AnimatedPropertySetter(config.duration, layerViews, anim));
+    }
+
+    private void setState(LauncherState state, PropertySetter setter) {
+        setter.setViewAlpha(null, mLauncher.getOverviewPanel(),
+                state == LauncherState.OVERVIEW ? 1 : 0);
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
new file mode 100644
index 0000000..20abdc7
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
@@ -0,0 +1,71 @@
+/*
+ * 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.uioverrides;
+
+import android.content.Intent;
+import android.view.View.AccessibilityDelegate;
+import android.widget.PopupMenu;
+import android.widget.Toast;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherStateManager.StateHandler;
+import com.android.launcher3.R;
+import com.android.launcher3.VerticalSwipeController;
+import com.android.launcher3.util.TouchController;
+import com.android.launcher3.widget.WidgetsFullSheet;
+
+public class UiFactory {
+
+    public static TouchController[] createTouchControllers(Launcher launcher) {
+        return new TouchController[] {new VerticalSwipeController(launcher)};
+    }
+
+    public static AccessibilityDelegate newPageIndicatorAccessibilityDelegate() {
+        return null;
+    }
+
+    public static StateHandler[] getStateHandler(Launcher launcher) {
+        return new StateHandler[] {
+                launcher.getAllAppsController(), launcher.getWorkspace(),
+                new RecentsViewStateController(launcher)};
+    }
+
+    public static void onWorkspaceLongPress(Launcher launcher) {
+        PopupMenu menu = new PopupMenu(launcher, launcher.getWorkspace().getPageIndicator());
+        menu.getMenu().add(R.string.wallpaper_button_text).setOnMenuItemClickListener((i) -> {
+            launcher.onClickWallpaperPicker(null);
+            return true;
+        });
+        menu.getMenu().add(R.string.widget_button_text).setOnMenuItemClickListener((i) -> {
+            if (launcher.getPackageManager().isSafeMode()) {
+                Toast.makeText(launcher, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
+            } else {
+                WidgetsFullSheet.show(launcher, true /* animated */);
+            }
+            return true;
+        });
+        if (launcher.hasSettings()) {
+            menu.getMenu().add(R.string.settings_button_text).setOnMenuItemClickListener((i) -> {
+                launcher.startActivity(new Intent(Intent.ACTION_APPLICATION_PREFERENCES)
+                        .setPackage(launcher.getPackageName())
+                        .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+                return true;
+            });
+        }
+        menu.show();
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/AnimatedFloat.java b/quickstep/src/com/android/quickstep/AnimatedFloat.java
new file mode 100644
index 0000000..1f6781e
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/AnimatedFloat.java
@@ -0,0 +1,79 @@
+/*
+ * 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.quickstep;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.util.FloatProperty;
+
+/**
+ * A mutable float which allows animating the value
+ */
+public class AnimatedFloat {
+
+    public static FloatProperty<AnimatedFloat> VALUE = new FloatProperty<AnimatedFloat>("value") {
+        @Override
+        public void setValue(AnimatedFloat obj, float v) {
+            obj.updateValue(v);
+        }
+
+        @Override
+        public Float get(AnimatedFloat obj) {
+            return obj.value;
+        }
+    };
+
+    private final Runnable mUpdateCallback;
+    private ObjectAnimator mValueAnimator;
+
+    public float value;
+
+    public AnimatedFloat(Runnable updateCallback) {
+        mUpdateCallback = updateCallback;
+    }
+
+    public ObjectAnimator animateToValue(float v) {
+        if (mValueAnimator != null) {
+            mValueAnimator.cancel();
+        }
+        mValueAnimator = ObjectAnimator.ofFloat(this, VALUE, v);
+        mValueAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animator) {
+                if (mValueAnimator == animator) {
+                    mValueAnimator = null;
+                }
+            }
+        });
+        return mValueAnimator;
+    }
+
+    /**
+     * Changes the value and calls the callback.
+     * Note that the value can be directly accessed as well to avoid notifying the callback.
+     */
+    public void updateValue(float v) {
+        if (Float.compare(v, value) != 0) {
+            value = v;
+            mUpdateCallback.run();
+        }
+    }
+
+    public ObjectAnimator getCurrentAnimation() {
+        return mValueAnimator;
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/MultiStateCallback.java b/quickstep/src/com/android/quickstep/MultiStateCallback.java
new file mode 100644
index 0000000..cca2729
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/MultiStateCallback.java
@@ -0,0 +1,58 @@
+/*
+ * 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.quickstep;
+
+import android.util.SparseArray;
+
+/**
+ * Utility class to help manage multiple callbacks based on different states.
+ */
+public class MultiStateCallback {
+
+    private final SparseArray<Runnable> mCallbacks = new SparseArray<>();
+
+    private int mState = 0;
+
+    /**
+     * Adds the provided state flags to the global state and executes any callbacks as a result.
+     * @param stateFlag
+     */
+    public void setState(int stateFlag) {
+        mState = mState | stateFlag;
+
+        int count = mCallbacks.size();
+        for (int i = 0; i < count; i++) {
+            int state = mCallbacks.keyAt(i);
+
+            if ((mState & state) == state) {
+                Runnable callback = mCallbacks.valueAt(i);
+                if (callback != null) {
+                    // Set the callback to null, so that it does not run again.
+                    mCallbacks.setValueAt(i, null);
+                    callback.run();
+                }
+            }
+        }
+    }
+
+    /**
+     * Sets the callbacks to be run when the provided states are enabled.
+     * The callback is only run once.
+     */
+    public void addCallback(int stateMask, Runnable callback) {
+        mCallbacks.put(stateMask, callback);
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java b/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java
new file mode 100644
index 0000000..dc7d648
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java
@@ -0,0 +1,366 @@
+/*
+ * 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.quickstep;
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.animation.RectEvaluator;
+import android.annotation.TargetApi;
+import android.app.ActivityManager.RunningTaskInfo;
+import android.app.ActivityOptions;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.os.Build;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.support.annotation.BinderThread;
+import android.support.annotation.UiThread;
+import android.util.DisplayMetrics;
+import android.view.Choreographer;
+import android.view.Choreographer.FrameCallback;
+import android.view.View;
+import android.view.ViewTreeObserver.OnPreDrawListener;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.Hotseat;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.states.InternalStateHandler;
+import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.Task.TaskKey;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.WindowManagerWrapper;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+@TargetApi(Build.VERSION_CODES.O)
+public class NavBarSwipeInteractionHandler extends InternalStateHandler implements FrameCallback {
+
+    private static final int STATE_LAUNCHER_READY = 1 << 0;
+    private static final int STATE_RECENTS_DELAY_COMPLETE = 1 << 1;
+    private static final int STATE_LOAD_PLAN_READY = 1 << 2;
+    private static final int STATE_RECENTS_FULLY_VISIBLE = 1 << 3;
+    private static final int STATE_ACTIVITY_MULTIPLIER_COMPLETE = 1 << 4;
+    private static final int STATE_SCALED_SNAPSHOT_RECENTS = 1 << 5;
+    private static final int STATE_SCALED_SNAPSHOT_APP = 1 << 6;
+
+    private static final long RECENTS_VIEW_VISIBILITY_DELAY = 120;
+    private static final long RECENTS_VIEW_VISIBILITY_DURATION = 150;
+    private static final long DEFAULT_SWIPE_DURATION = 200;
+
+    // Ideal velocity for a smooth transition
+    private static final float PIXEL_PER_MS = 2f;
+
+    private static final float MIN_PROGRESS_FOR_OVERVIEW = 0.5f;
+
+    private final Rect mStableInsets = new Rect();
+    private final Rect mSourceRect = new Rect();
+    private final Rect mTargetRect = new Rect();
+    private final Rect mCurrentRect = new Rect();
+    private final RectEvaluator mRectEvaluator = new RectEvaluator(mCurrentRect);
+
+    // Shift in the range of [0, 1].
+    // 0 => preview snapShot is completely visible, and hotseat is completely translated down
+    // 1 => preview snapShot is completely aligned with the recents view and hotseat is completely
+    // visible.
+    private final AnimatedFloat mCurrentShift = new AnimatedFloat(this::updateFinalShift);
+
+    // Activity multiplier in the range of [0, 1]. When the activity becomes visible, this is
+    // animated to 1, so allow for a smooth transition.
+    private final AnimatedFloat mActivityMultiplier = new AnimatedFloat(this::updateFinalShift);
+
+    private final Choreographer mChoreographer;
+    private final AtomicBoolean mFrameScheduled = new AtomicBoolean(false);
+
+    private final int mRunningTaskId;
+    private final Context mContext;
+
+    private final MultiStateCallback mStateCallback;
+
+    private Launcher mLauncher;
+    private SnapshotDragView mDragView;
+    private RecentsView mRecentsView;
+    private Hotseat mHotseat;
+    private RecentsTaskLoadPlan mLoadPlan;
+
+    private boolean mLauncherReady;
+    private boolean mTouchEndHandled;
+
+    private Bitmap mTaskSnapshot;
+
+    // These are updated on the binder thread, and eventually picked up on doFrame
+    private volatile float mCurrentDisplacement;
+    private volatile float mEndVelocity;
+    private volatile boolean mTouchEnded = false;
+
+    NavBarSwipeInteractionHandler(
+            RunningTaskInfo runningTaskInfo, Choreographer choreographer, Context context) {
+        mRunningTaskId = runningTaskInfo.id;
+        mChoreographer = choreographer;
+        mContext = context;
+        WindowManagerWrapper.getInstance().getStableInsets(mStableInsets);
+
+        // Build the state callback
+        mStateCallback = new MultiStateCallback();
+        mStateCallback.addCallback(STATE_LAUNCHER_READY, this::onLauncherReady);
+        mStateCallback.addCallback(STATE_LOAD_PLAN_READY | STATE_RECENTS_DELAY_COMPLETE,
+                this::setTaskPlanToUi);
+        mStateCallback.addCallback(STATE_SCALED_SNAPSHOT_APP, this::resumeLastTask);
+        mStateCallback.addCallback(STATE_RECENTS_FULLY_VISIBLE | STATE_SCALED_SNAPSHOT_RECENTS
+                | STATE_ACTIVITY_MULTIPLIER_COMPLETE,
+                this::onAnimationToLauncherComplete);
+        mStateCallback.addCallback(STATE_LAUNCHER_READY | STATE_SCALED_SNAPSHOT_APP,
+                this::cleanupLauncher);
+    }
+
+    private void onLauncherReady() {
+        mLauncherReady = true;
+        executeFrameUpdate();
+
+        // Wait for some time before loading recents so that the first frame is fast
+        new Handler().postDelayed(() -> mStateCallback.setState(STATE_RECENTS_DELAY_COMPLETE),
+                RECENTS_VIEW_VISIBILITY_DELAY);
+
+        long duration = Math.min(DEFAULT_SWIPE_DURATION,
+                Math.max((long) (-mCurrentDisplacement / PIXEL_PER_MS), 0));
+        if (mCurrentShift.getCurrentAnimation() != null) {
+            ObjectAnimator anim = mCurrentShift.getCurrentAnimation();
+            long theirDuration = anim.getDuration() - anim.getCurrentPlayTime();
+
+            // TODO: Find a better heuristic
+            duration = (duration + theirDuration) / 2;
+        }
+        ObjectAnimator anim = mActivityMultiplier.animateToValue(1)
+                .setDuration(duration);
+        anim.addListener(new AnimationSuccessListener() {
+            @Override
+            public void onAnimationSuccess(Animator animator) {
+                mStateCallback.setState(STATE_ACTIVITY_MULTIPLIER_COMPLETE);
+            }
+        });
+        anim.start();
+    }
+
+    public void setTaskSnapshot(Bitmap taskSnapshot) {
+        mTaskSnapshot = taskSnapshot;
+    }
+
+    @Override
+    public void onLauncherResume() {
+        mDragView.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
+            @Override
+            public boolean onPreDraw() {
+                mDragView.getViewTreeObserver().removeOnPreDrawListener(this);
+                mStateCallback.setState(STATE_LAUNCHER_READY);
+                return true;
+            }
+        });
+    }
+
+    @Override
+    protected void init(Launcher launcher, boolean alreadyOnHome) {
+        AbstractFloatingView.closeAllOpenViews(launcher, alreadyOnHome);
+        launcher.getStateManager().goToState(LauncherState.OVERVIEW, alreadyOnHome);
+
+        mLauncher = launcher;
+        mDragView = new SnapshotDragView(mLauncher, mTaskSnapshot);
+        mLauncher.getDragLayer().addView(mDragView);
+        mDragView.setPivotX(0);
+        mDragView.setPivotY(0);
+        mRecentsView = mLauncher.getOverviewPanel();
+        mHotseat = mLauncher.getHotseat();
+
+        // Optimization
+        mLauncher.getAppsView().setVisibility(View.GONE);
+        mRecentsView.setVisibility(View.GONE);
+    }
+
+    /**
+     * This is updated on the binder thread and is picked up on the UI thread during the next
+     * scheduled frame.
+     * TODO: Instead of continuously scheduling frames, post the motion events to UI thread
+     * (can ignore all continuous move events until the last move).
+     */
+    @BinderThread
+    public void updateDisplacement(float displacement) {
+        mCurrentDisplacement = displacement;
+        scheduleFrameIfNeeded();
+    }
+
+    @BinderThread
+    public void endTouch(float endVelocity) {
+        mEndVelocity = endVelocity;
+        mTouchEnded = true;
+        scheduleFrameIfNeeded();
+    }
+
+    private void scheduleFrameIfNeeded() {
+        boolean alreadyScheduled = mFrameScheduled.getAndSet(true);
+        if (!alreadyScheduled) {
+            // TODO: Here we might end up scheduling one additional frame in some race conditions.
+            // This can be avoided by synchronising postFrameCallback as well
+            mChoreographer.postFrameCallback(this);
+        }
+    }
+
+    @Override
+    public void doFrame(long l) {
+        mFrameScheduled.set(false);
+        executeFrameUpdate();
+    }
+
+    private void executeFrameUpdate() {
+        if (mLauncherReady) {
+            final float displacement = -mCurrentDisplacement;
+            int hotseatHeight = mHotseat.getHeight();
+            float translation = Utilities.boundToRange(displacement, 0, hotseatHeight);
+            float shift = hotseatHeight == 0 ? 0 : translation / hotseatHeight;
+            mCurrentShift.updateValue(shift);
+        }
+
+        if (mTouchEnded) {
+            if (mTouchEndHandled) {
+                return;
+            }
+            mTouchEndHandled = true;
+            animateToFinalShift();
+        }
+    }
+
+    @UiThread
+    private void updateFinalShift() {
+        if (!mLauncherReady) {
+            return;
+        }
+
+        if (mTargetRect.isEmpty()) {
+            DragLayer dl = mLauncher.getDragLayer();
+            mSourceRect.set(0, 0, dl.getWidth(), dl.getHeight());
+            Rect targetPadding = RecentsView.getPadding(mLauncher);
+            Rect insets = dl.getInsets();
+            mTargetRect.set(
+                    targetPadding.left + insets.left,
+                    targetPadding.top + insets.top,
+                    mSourceRect.right - targetPadding.right - insets.right,
+                    mSourceRect.bottom - targetPadding.bottom - insets.bottom);
+            mTargetRect.top += mLauncher.getResources()
+                    .getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
+        }
+
+        float shift = mCurrentShift.value * mActivityMultiplier.value;
+        int hotseatHeight = mHotseat.getHeight();
+
+        mHotseat.setTranslationY((1 - shift) * hotseatHeight);
+
+        mRectEvaluator.evaluate(shift, mSourceRect, mTargetRect);
+
+        float scale = (float) mCurrentRect.width() / mSourceRect.width();
+        mDragView.setTranslationX(mCurrentRect.left - mStableInsets.left * scale * shift);
+        mDragView.setTranslationY(mCurrentRect.top - mStableInsets.top * scale * shift);
+        mDragView.setScaleX(scale);
+        mDragView.setScaleY(scale);
+        mDragView.getViewBounds().setClipTop((int) (mStableInsets.top * shift));
+        mDragView.getViewBounds().setClipBottom((int) (mStableInsets.bottom * shift));
+    }
+
+    @UiThread
+    public void setRecentsTaskLoadPlan(RecentsTaskLoadPlan loadPlan) {
+        mLoadPlan = loadPlan;
+        mStateCallback.setState(STATE_LOAD_PLAN_READY);
+    }
+
+    private void setTaskPlanToUi() {
+        mRecentsView.update(mLoadPlan);
+        mRecentsView.setVisibility(View.VISIBLE);
+
+        // Animate alpha
+        mRecentsView.setAlpha(0);
+        mRecentsView.animate().alpha(1).setDuration(RECENTS_VIEW_VISIBILITY_DURATION)
+                .withEndAction(() -> mStateCallback.setState(STATE_RECENTS_FULLY_VISIBLE));
+    }
+
+    @UiThread
+    private void animateToFinalShift() {
+        Resources res = mContext.getResources();
+        float flingThreshold = res.getDimension(R.dimen.quickstep_fling_threshold_velocity);
+        boolean isFling = Math.abs(mEndVelocity) > flingThreshold;
+
+        long duration = DEFAULT_SWIPE_DURATION;
+        final float endShift;
+        if (!isFling) {
+            endShift = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW ? 1 : 0;
+        } else {
+            endShift = mEndVelocity < 0 ? 1 : 0;
+            float minFlingVelocity = res.getDimension(R.dimen.quickstep_fling_min_velocity);
+            if (Math.abs(mEndVelocity) > minFlingVelocity && mLauncherReady) {
+                float distanceToTravel = (endShift - mCurrentShift.value) * mHotseat.getHeight();
+
+                // we want the page's snap velocity to approximately match the velocity at
+                // which the user flings, so we scale the duration by a value near to the
+                // derivative of the scroll interpolator at zero, ie. 5.
+                duration = 5 * Math.round(1000 * Math.abs(distanceToTravel / mEndVelocity));
+            }
+        }
+
+        ObjectAnimator anim = mCurrentShift.animateToValue(endShift).setDuration(duration);
+        anim.setInterpolator(Interpolators.SCROLL);
+        anim.addListener(new AnimationSuccessListener() {
+            @Override
+            public void onAnimationSuccess(Animator animator) {
+                mStateCallback.setState((Float.compare(mCurrentShift.value, 0) == 0)
+                        ? STATE_SCALED_SNAPSHOT_APP : STATE_SCALED_SNAPSHOT_RECENTS);
+            }
+        });
+        anim.start();
+    }
+
+    @UiThread
+    private void resumeLastTask() {
+        TaskKey key = null;
+        if (mLoadPlan != null) {
+            Task task = mLoadPlan.getTaskStack().findTaskWithId(mRunningTaskId);
+            if (task != null) {
+                key = task.key;
+            }
+        }
+
+        if (key == null) {
+            // TODO: We need a better way for this
+            key = new TaskKey(mRunningTaskId, 0, null, UserHandle.myUserId(), 0);
+        }
+
+        ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext, 0, 0);
+        ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(key, opts, null, null);
+    }
+
+    private void cleanupLauncher() {
+        // TODO: These should be done as part of ActivityOptions#OnAnimationStarted
+        mHotseat.setTranslationY(0);
+        mLauncher.setOnResumeCallback(() -> mDragView.close(false));
+    }
+
+    private void onAnimationToLauncherComplete() {
+        mDragView.close(false);
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
new file mode 100644
index 0000000..f92d773
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -0,0 +1,48 @@
+/*
+ * 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.quickstep;
+
+import android.app.ListActivity;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.support.annotation.Nullable;
+import android.widget.ArrayAdapter;
+
+import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
+import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan.PreloadOptions;
+import com.android.systemui.shared.recents.model.RecentsTaskLoader;
+import com.android.systemui.shared.recents.model.Task;
+
+/**
+ * A simple activity to show the recently launched tasks
+ */
+public class RecentsActivity extends ListActivity {
+
+    private ArrayAdapter<Task> mAdapter;
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(this);
+        plan.preloadPlan(new PreloadOptions(), new RecentsTaskLoader(this, 1, 1, 0), -1,
+                UserHandle.myUserId());
+
+        mAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1);
+        mAdapter.addAll(plan.getTaskStack().getTasks());
+        setListAdapter(mAdapter);
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/RecentsView.java b/quickstep/src/com/android/quickstep/RecentsView.java
new file mode 100644
index 0000000..675f456
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/RecentsView.java
@@ -0,0 +1,173 @@
+/*
+ * 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.quickstep;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.PagedView;
+import com.android.launcher3.R;
+import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
+import com.android.systemui.shared.recents.model.RecentsTaskLoader;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.TaskStack;
+import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.WindowManagerWrapper;
+
+import java.util.ArrayList;
+
+/**
+ * A list of recent tasks.
+ */
+public class RecentsView extends PagedView {
+
+    private boolean mOverviewStateEnabled;
+    private boolean mTaskStackListenerRegistered;
+
+    private TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
+        @Override
+        public void onTaskSnapshotChanged(int taskId, ThumbnailData snapshot) {
+            for (int i = 0; i < getChildCount(); i++) {
+                final TaskView taskView = (TaskView) getChildAt(i);
+                if (taskView.getTask().key.id == taskId) {
+                    taskView.getThumbnail().setThumbnail(snapshot);
+                    return;
+                }
+            }
+        }
+    };
+
+    public RecentsView(Context context) {
+        this(context, null);
+    }
+
+    public RecentsView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public RecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        setWillNotDraw(false);
+        setPageSpacing((int) getResources().getDimension(R.dimen.recents_page_spacing));
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        Rect padding = getPadding(Launcher.getLauncher(getContext()));
+        setPadding(padding.left, padding.top, padding.right, padding.bottom);
+    }
+
+    @Override
+    protected void onWindowVisibilityChanged(int visibility) {
+        super.onWindowVisibilityChanged(visibility);
+        updateTaskStackListenerState();
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        updateTaskStackListenerState();
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        updateTaskStackListenerState();
+    }
+
+    public void setOverviewStateEnabled(boolean enabled) {
+        mOverviewStateEnabled = enabled;
+        updateTaskStackListenerState();
+    }
+
+    public void update(RecentsTaskLoadPlan loadPlan) {
+        final RecentsTaskLoader loader = TouchInteractionService.getRecentsTaskLoader();
+        setCurrentPage(0);
+        TaskStack stack = loadPlan != null ? loadPlan.getTaskStack() : null;
+        if (stack == null) {
+            removeAllViews();
+            return;
+        }
+
+        // Ensure there are as many views as there are tasks in the stack (adding and trimming as
+        // necessary)
+        final LayoutInflater inflater = LayoutInflater.from(getContext());
+        final ArrayList<Task> tasks = stack.getTasks();
+        for (int i = getChildCount(); i < tasks.size(); i++) {
+            final TaskView taskView = (TaskView) inflater.inflate(R.layout.task, this, false);
+            addView(taskView);
+        }
+        while (getChildCount() > tasks.size()) {
+            final TaskView taskView = (TaskView) getChildAt(getChildCount() - 1);
+            removeView(taskView);
+            loader.unloadTaskData(taskView.getTask());
+        }
+
+        // Rebind all task views
+        for (int i = tasks.size() - 1; i >= 0; i--) {
+            final Task task = tasks.get(i);
+            final TaskView taskView = (TaskView) getChildAt(tasks.size() - i - 1);
+            taskView.bind(task);
+            loader.loadTaskData(task);
+        }
+    }
+
+    public void launchTaskWithId(int taskId) {
+        for (int i = 0; i < getChildCount(); i++) {
+            final TaskView taskView = (TaskView) getChildAt(i);
+            if (taskView.getTask().key.id == taskId) {
+                taskView.launchTask(false /* animate */);
+                return;
+            }
+        }
+    }
+
+    private void updateTaskStackListenerState() {
+        boolean registerStackListener = mOverviewStateEnabled && isAttachedToWindow()
+                && getWindowVisibility() == VISIBLE;
+        if (registerStackListener != mTaskStackListenerRegistered) {
+            if (registerStackListener) {
+                ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
+            } else {
+                ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskStackListener);
+            }
+            mTaskStackListenerRegistered = registerStackListener;
+        }
+    }
+
+    public static Rect getPadding(Launcher launcher) {
+        DeviceProfile profile = launcher.getDeviceProfile();
+        Rect stableInsets = new Rect();
+        WindowManagerWrapper.getInstance().getStableInsets(stableInsets);
+        Rect padding = profile.getWorkspacePadding(null);
+        float taskWidth = profile.getCurrentWidth() - stableInsets.left - stableInsets.right;
+        float taskHeight = profile.getCurrentHeight() - stableInsets.top - stableInsets.bottom;
+        float overviewHeight = profile.availableHeightPx - padding.top - padding.bottom
+                - stableInsets.top;
+        float overviewWidth = taskWidth * overviewHeight / taskHeight;
+        padding.left = padding.right = (int) ((profile.availableWidthPx - overviewWidth) / 2);
+        return padding;
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/SimpleTaskView.java b/quickstep/src/com/android/quickstep/SimpleTaskView.java
new file mode 100644
index 0000000..8425fa3
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/SimpleTaskView.java
@@ -0,0 +1,52 @@
+/*
+ * 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.quickstep;
+
+import android.content.Context;
+import android.graphics.Point;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.WindowManager;
+
+/**
+ * A simple view which keeps its size proportional to the display size
+ */
+public class SimpleTaskView extends View {
+
+    private static final Point sTempPoint = new Point();
+
+    public SimpleTaskView(Context context) {
+        super(context);
+    }
+
+    public SimpleTaskView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public SimpleTaskView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int height = MeasureSpec.getSize(heightMeasureSpec);
+        getContext().getSystemService(WindowManager.class)
+                .getDefaultDisplay().getRealSize(sTempPoint);
+
+        int width = (int) ((float) height * sTempPoint.x / sTempPoint.y);
+        setMeasuredDimension(width, height);
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/SnapshotDragView.java b/quickstep/src/com/android/quickstep/SnapshotDragView.java
new file mode 100644
index 0000000..2ef3942
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/SnapshotDragView.java
@@ -0,0 +1,92 @@
+/*
+ * 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.quickstep;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.view.MotionEvent;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.Insettable;
+import com.android.launcher3.Launcher;
+import com.android.systemui.shared.recents.view.AnimateableViewBounds;
+
+/**
+ * Floating view which shows the task snapshot allowing it to be dragged and placed.
+ */
+public class SnapshotDragView extends AbstractFloatingView implements Insettable {
+
+    private final Launcher mLauncher;
+    private final Bitmap mSnapshot;
+    private final AnimateableViewBounds mViewBounds;
+
+    public SnapshotDragView(Launcher launcher, Bitmap snapshot) {
+        super(launcher, null);
+        mLauncher = launcher;
+        mSnapshot = snapshot;
+        mViewBounds = new AnimateableViewBounds(this, 0);
+        setWillNotDraw(false);
+        setClipToOutline(true);
+        setOutlineProvider(mViewBounds);
+    }
+
+    AnimateableViewBounds getViewBounds() {
+        return mViewBounds;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        if (mSnapshot != null) {
+            setMeasuredDimension(mSnapshot.getWidth(), mSnapshot.getHeight());
+        } else {
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        }
+    }
+
+    @Override
+    public void setInsets(Rect insets) {
+
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        if (mSnapshot != null) {
+            canvas.drawBitmap(mSnapshot, 0, 0, null);
+        }
+    }
+
+    @Override
+    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+        return false;
+    }
+
+    @Override
+    protected void handleClose(boolean animate) {
+        // We dont suupport animate.
+        mLauncher.getDragLayer().removeView(this);
+    }
+
+    @Override
+    public void logActionCommand(int command) {
+        // We should probably log the weather
+    }
+
+    @Override
+    protected boolean isOfType(int type) {
+        return (type & TYPE_QUICKSTEP_PREVIEW) != 0;
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/TaskThumbnailView.java
new file mode 100644
index 0000000..6e8bbeb
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/TaskThumbnailView.java
@@ -0,0 +1,179 @@
+/*
+ * 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.quickstep;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.LightingColorFilter;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.Shader;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.systemui.shared.recents.model.ThumbnailData;
+
+/**
+ * A task in the Recents view.
+ */
+public class TaskThumbnailView extends FrameLayout {
+
+    private ThumbnailData mThumbnailData;
+
+    private Rect mThumbnailRect = new Rect();
+    private float mThumbnailScale;
+
+    private Matrix mMatrix = new Matrix();
+    private Paint mDrawPaint = new Paint();
+    protected Paint mBgFillPaint = new Paint();
+    protected BitmapShader mBitmapShader;
+
+    private float mDimAlpha = 1f;
+    private LightingColorFilter mLightingColorFilter = new LightingColorFilter(Color.WHITE, 0);
+
+    public TaskThumbnailView(Context context) {
+        this(context, null);
+    }
+
+    public TaskThumbnailView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public TaskThumbnailView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        setWillNotDraw(false);
+        setClipToOutline(true);
+    }
+
+    /**
+     * Updates this thumbnail.
+     */
+    public void setThumbnail(ThumbnailData thumbnailData) {
+        if (thumbnailData != null && thumbnailData.thumbnail != null) {
+            Bitmap bm = thumbnailData.thumbnail;
+            bm.prepareToDraw();
+            mThumbnailScale = thumbnailData.scale;
+            mBitmapShader = new BitmapShader(bm, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
+            mDrawPaint.setShader(mBitmapShader);
+            mThumbnailRect.set(0, 0,
+                    bm.getWidth() - thumbnailData.insets.left - thumbnailData.insets.right,
+                    bm.getHeight() - thumbnailData.insets.top - thumbnailData.insets.bottom);
+            mThumbnailData = thumbnailData;
+            updateThumbnailMatrix();
+            updateThumbnailPaintFilter();
+        } else {
+            mBitmapShader = null;
+            mDrawPaint.setShader(null);
+            mThumbnailRect.setEmpty();
+            mThumbnailData = null;
+        }
+    }
+
+    /**
+     * Sets the alpha of the dim layer on top of this view.
+     */
+    public void setDimAlpha(float dimAlpha) {
+        mDimAlpha = dimAlpha;
+        updateThumbnailPaintFilter();
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        int viewWidth = getMeasuredWidth();
+        int viewHeight = getMeasuredHeight();
+        int thumbnailWidth = Math.min(viewWidth,
+                (int) (mThumbnailRect.width() * mThumbnailScale));
+        int thumbnailHeight = Math.min(viewHeight,
+                (int) (mThumbnailRect.height() * mThumbnailScale));
+
+        if (mBitmapShader != null && thumbnailWidth > 0 && thumbnailHeight > 0) {
+            // Draw the background, there will be some small overdraw with the thumbnail
+            if (thumbnailWidth < viewWidth) {
+                // Portrait thumbnail on a landscape task view
+                canvas.drawRect(Math.max(0, thumbnailWidth), 0, viewWidth, viewHeight,
+                        mBgFillPaint);
+            }
+            if (thumbnailHeight < viewHeight) {
+                // Landscape thumbnail on a portrait task view
+                canvas.drawRect(0, Math.max(0, thumbnailHeight), viewWidth, viewHeight,
+                        mBgFillPaint);
+            }
+
+            // Draw the thumbnail
+            canvas.drawRect(0, 0, thumbnailWidth, thumbnailHeight, mDrawPaint);
+        } else {
+            canvas.drawRect(0, 0, viewWidth, viewHeight, mBgFillPaint);
+        }
+    }
+
+    private void updateThumbnailPaintFilter() {
+        int mul = (int) (mDimAlpha * 255);
+        if (mBitmapShader != null) {
+            mLightingColorFilter = new LightingColorFilter(Color.argb(255, mul, mul, mul), 0);
+            mDrawPaint.setColorFilter(mLightingColorFilter);
+            mDrawPaint.setColor(0xFFffffff);
+            mBgFillPaint.setColorFilter(mLightingColorFilter);
+        } else {
+            mDrawPaint.setColorFilter(null);
+            mDrawPaint.setColor(Color.argb(255, mul, mul, mul));
+        }
+        invalidate();
+    }
+
+    private void updateThumbnailMatrix() {
+        mThumbnailScale = 1f;
+        if (mBitmapShader != null && mThumbnailData != null) {
+            if (getMeasuredWidth() == 0) {
+                // If we haven't measured , skip the thumbnail drawing and only draw the background
+                // color
+                mThumbnailScale = 0f;
+            } else {
+                float invThumbnailScale = 1f / mThumbnailScale;
+                final Configuration configuration =
+                        getContext().getApplicationContext().getResources().getConfiguration();
+                final DeviceProfile profile = Launcher.getLauncher(getContext()).getDeviceProfile();
+                if (configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
+                    if (mThumbnailData.orientation == Configuration.ORIENTATION_PORTRAIT) {
+                        // If we are in the same orientation as the screenshot, just scale it to the
+                        // width of the task view
+                        mThumbnailScale = (float) getMeasuredWidth() / mThumbnailRect.width();
+                    } else {
+                        // Scale the landscape thumbnail up to app size, then scale that to the task
+                        // view size to match other portrait screenshots
+                        mThumbnailScale = invThumbnailScale *
+                                ((float) getMeasuredWidth() / profile.getCurrentWidth());
+                    }
+                } else {
+                    // Otherwise, scale the screenshot to fit 1:1 in the current orientation
+                    mThumbnailScale = invThumbnailScale;
+                }
+            }
+            mMatrix.setTranslate(-mThumbnailData.insets.left, -mThumbnailData.insets.top);
+            mMatrix.postScale(mThumbnailScale, mThumbnailScale);
+            mBitmapShader.setLocalMatrix(mMatrix);
+        }
+        invalidate();
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/TaskView.java b/quickstep/src/com/android/quickstep/TaskView.java
new file mode 100644
index 0000000..029afd6
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/TaskView.java
@@ -0,0 +1,136 @@
+/*
+ * 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.quickstep;
+
+import android.app.ActivityOptions;
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+import com.android.launcher3.R;
+import com.android.launcher3.uioverrides.OverviewState;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.Task.TaskCallbacks;
+import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat;
+import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
+import com.android.systemui.shared.recents.view.RecentsTransition;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A task in the Recents view.
+ */
+public class TaskView extends FrameLayout implements TaskCallbacks {
+
+    private Task mTask;
+    private TaskThumbnailView mSnapshotView;
+    private ImageView mIconView;
+
+    public TaskView(Context context) {
+        this(context, null);
+    }
+
+    public TaskView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public TaskView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        setOnClickListener((view) -> {
+            launchTask(true /* animate */);
+        });
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        mSnapshotView = findViewById(R.id.snapshot);
+        mIconView = findViewById(R.id.icon);
+    }
+
+    /**
+     * Updates this task view to the given {@param task}.
+     */
+    public void bind(Task task) {
+        if (mTask != null) {
+            mTask.removeCallback(this);
+        }
+        mTask = task;
+        task.addCallback(this);
+    }
+
+    public Task getTask() {
+        return mTask;
+    }
+
+    public TaskThumbnailView getThumbnail() {
+        return mSnapshotView;
+    }
+
+    public void launchTask(boolean animate) {
+        if (mTask != null) {
+            final ActivityOptions opts;
+            if (animate) {
+                // Calculate the bounds of the thumbnail to animate from
+                final Rect bounds = new Rect();
+                final int[] pos = new int[2];
+                mSnapshotView.getLocationInWindow(pos);
+                bounds.set(pos[0], pos[1],
+                        pos[0] + mSnapshotView.getWidth(),
+                        pos[1] + mSnapshotView.getHeight());
+                AppTransitionAnimationSpecsFuture animFuture =
+                        new AppTransitionAnimationSpecsFuture(getHandler()) {
+                            @Override
+                            public List<AppTransitionAnimationSpecCompat> composeSpecs() {
+                                ArrayList<AppTransitionAnimationSpecCompat> specs =
+                                        new ArrayList<>();
+                                specs.add(new AppTransitionAnimationSpecCompat(mTask.key.id, null,
+                                        bounds));
+                                return specs;
+                            }
+                        };
+                opts = RecentsTransition.createAspectScaleAnimation(
+                        getContext(), getHandler(), true /* scaleUp */, animFuture, null);
+            } else {
+                opts = ActivityOptions.makeCustomAnimation(getContext(), 0, 0);
+            }
+            ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(mTask.key,
+                    opts, null, null);
+        }
+    }
+
+    @Override
+    public void onTaskDataLoaded(Task task, ThumbnailData thumbnailData) {
+        mSnapshotView.setThumbnail(thumbnailData);
+        mIconView.setImageDrawable(task.icon);
+    }
+
+    @Override
+    public void onTaskDataUnloaded() {
+        mSnapshotView.setThumbnail(null);
+        mIconView.setImageDrawable(null);
+    }
+
+    @Override
+    public void onTaskWindowingModeChanged() {
+        // Do nothing
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
new file mode 100644
index 0000000..b57a417
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -0,0 +1,265 @@
+/*
+ * 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.quickstep;
+
+import static android.view.MotionEvent.INVALID_POINTER_ID;
+
+import android.app.ActivityManager.RunningTaskInfo;
+import android.app.ActivityOptions;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+import android.view.Choreographer;
+import android.view.Display;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.ViewConfiguration;
+import android.view.WindowManager;
+
+import com.android.launcher3.MainThreadExecutor;
+import com.android.launcher3.R;
+import com.android.systemui.shared.recents.IOverviewProxy;
+import com.android.systemui.shared.recents.ISystemUiProxy;
+import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
+import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan.PreloadOptions;
+import com.android.systemui.shared.recents.model.RecentsTaskLoader;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.BackgroundExecutor;
+
+/**
+ * Service connected by system-UI for handling touch interaction.
+ */
+public class TouchInteractionService extends Service {
+
+    private static final String TAG = "TouchInteractionService";
+
+    private static RecentsTaskLoader sRecentsTaskLoader;
+
+    private final IBinder mMyBinder = new IOverviewProxy.Stub() {
+
+        @Override
+        public void onMotionEvent(MotionEvent ev) {
+            handleMotionEvent(ev);
+        }
+
+        @Override
+        public void onBind(ISystemUiProxy iSystemUiProxy) throws RemoteException {
+            mISystemUiProxy = iSystemUiProxy;
+        }
+    };
+
+    private ActivityManagerWrapper mAM;
+    private RunningTaskInfo mRunningTask;
+    private Intent mHomeIntent;
+    private ComponentName mLauncher;
+    private Choreographer mChoreographer;
+    private MainThreadExecutor mMainThreadExecutor;
+
+    private int mDisplayRotation;
+    private final Point mDisplaySize = new Point();
+    private final PointF mDownPos = new PointF();
+    private final PointF mLastPos = new PointF();
+    private int mActivePointerId = INVALID_POINTER_ID;
+    private VelocityTracker mVelocityTracker;
+    private int mTouchSlop;
+    private NavBarSwipeInteractionHandler mInteractionHandler;
+
+    private ISystemUiProxy mISystemUiProxy;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mAM = ActivityManagerWrapper.getInstance();
+
+        mHomeIntent = new Intent(Intent.ACTION_MAIN)
+                .addCategory(Intent.CATEGORY_HOME)
+                .setPackage(getPackageName())
+                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        ResolveInfo info = getPackageManager().resolveActivity(mHomeIntent, 0);
+        mLauncher = new ComponentName(getPackageName(), info.activityInfo.name);
+        mHomeIntent.setComponent(mLauncher);
+
+        Resources res = getResources();
+        if (sRecentsTaskLoader == null) {
+            sRecentsTaskLoader = new RecentsTaskLoader(this,
+                    res.getInteger(R.integer.config_recentsMaxThumbnailCacheSize),
+                    res.getInteger(R.integer.config_recentsMaxIconCacheSize), 0);
+            sRecentsTaskLoader.startLoader(this);
+        }
+
+        mChoreographer = Choreographer.getInstance();
+        mMainThreadExecutor = new MainThreadExecutor();
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        Log.d(TAG, "Touch service connected");
+        return mMyBinder;
+    }
+
+    public static RecentsTaskLoader getRecentsTaskLoader() {
+        return sRecentsTaskLoader;
+    }
+
+    private void handleMotionEvent(MotionEvent ev) {
+        if (ev.getActionMasked() != MotionEvent.ACTION_DOWN && mVelocityTracker == null) {
+            return;
+        }
+        switch (ev.getActionMasked()) {
+            case MotionEvent.ACTION_DOWN: {
+                mActivePointerId = ev.getPointerId(0);
+                mDownPos.set(ev.getX(), ev.getY());
+                mLastPos.set(mDownPos);
+                mTouchSlop = ViewConfiguration.get(this).getScaledTouchSlop();
+                Display display = getSystemService(WindowManager.class).getDefaultDisplay();
+                display.getRealSize(mDisplaySize);
+                mDisplayRotation = display.getRotation();
+
+                mRunningTask = mAM.getRunningTask();
+                if (mRunningTask == null || mRunningTask.topActivity.equals(mLauncher)) {
+                    // TODO: We could drive all-apps in this case. For now just ignore swipe.
+                    break;
+                }
+
+                if (mVelocityTracker == null) {
+                    mVelocityTracker = VelocityTracker.obtain();
+                } else {
+                    mVelocityTracker.clear();
+                }
+                mVelocityTracker.addMovement(ev);
+                if (mInteractionHandler != null) {
+                    mInteractionHandler.endTouch(0);
+                    mInteractionHandler = null;
+                }
+                break;
+            }
+            case MotionEvent.ACTION_POINTER_UP: {
+                int ptrIdx = ev.getActionIndex();
+                int ptrId = ev.getPointerId(ptrIdx);
+                if (ptrId == mActivePointerId) {
+                    final int newPointerIdx = ptrIdx == 0 ? 1 : 0;
+                    mDownPos.set(
+                            ev.getX(newPointerIdx) - (mLastPos.x - mDownPos.x),
+                            ev.getY(newPointerIdx) - (mLastPos.y - mDownPos.y));
+                    mLastPos.set(ev.getX(newPointerIdx), ev.getY(newPointerIdx));
+                    mActivePointerId = ev.getPointerId(newPointerIdx);
+                    mVelocityTracker.clear();
+                }
+                break;
+            }
+            case MotionEvent.ACTION_MOVE: {
+                int pointerIndex = ev.findPointerIndex(mActivePointerId);
+                if (pointerIndex == INVALID_POINTER_ID) {
+                    break;
+                }
+                mVelocityTracker.addMovement(ev);
+                mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
+
+                float displacement = ev.getY(pointerIndex) - mDownPos.y;
+                if (mInteractionHandler == null) {
+                    if (Math.abs(displacement) >= mTouchSlop) {
+                        startTouchTracking();
+                    }
+                } else {
+                    // Move
+                    mInteractionHandler.updateDisplacement(displacement);
+                }
+                break;
+            }
+            case MotionEvent.ACTION_CANCEL:
+                // TODO: Should be different than ACTION_UP
+            case MotionEvent.ACTION_UP: {
+
+                endInteraction();
+                break;
+            }
+        }
+    }
+
+    private void startTouchTracking() {
+        // Create the shared handler
+        final NavBarSwipeInteractionHandler handler =
+                new NavBarSwipeInteractionHandler(mRunningTask, mChoreographer, this);
+
+        // Preload and start the recents activity on a background thread
+        final Context context = this;
+        final RecentsTaskLoadPlan loadPlan = new RecentsTaskLoadPlan(context);
+        final int taskId = mRunningTask.id;
+
+        BackgroundExecutor.get().submit(() -> {
+            // Get the snap shot before
+            handler.setTaskSnapshot(getCurrentTaskSnapshot());
+
+            // Start the launcher activity with our custom handler
+            Intent homeIntent = handler.addToIntent(new Intent(mHomeIntent));
+            startActivity(homeIntent, ActivityOptions.makeCustomAnimation(this, 0, 0).toBundle());
+
+            /*
+            ActivityManagerWrapper.getInstance().startRecentsActivity(null, options,
+                    ActivityOptions.makeCustomAnimation(this, 0, 0), UserHandle.myUserId(),
+                    null, null);
+             */
+
+            // Preload the plan
+            RecentsTaskLoader loader = TouchInteractionService.getRecentsTaskLoader();
+            PreloadOptions opts = new PreloadOptions();
+            opts.loadTitles = false;
+            loadPlan.preloadPlan(opts, loader, taskId, UserHandle.myUserId());
+            // Set the load plan on UI thread
+            mMainThreadExecutor.execute(() -> handler.setRecentsTaskLoadPlan(loadPlan));
+        });
+        mInteractionHandler = handler;
+    }
+
+    private void endInteraction() {
+        if (mInteractionHandler != null) {
+            mVelocityTracker.computeCurrentVelocity(1000,
+                    ViewConfiguration.get(this).getScaledMaximumFlingVelocity());
+
+            mInteractionHandler.endTouch(mVelocityTracker.getYVelocity(mActivePointerId));
+            mInteractionHandler = null;
+        }
+        mVelocityTracker.recycle();
+        mVelocityTracker = null;
+    }
+
+    private Bitmap getCurrentTaskSnapshot() {
+        if (mISystemUiProxy == null) {
+            Log.e(TAG, "Never received systemUIProxy");
+            return null;
+        }
+
+        // TODO: We are using some hardcoded layers for now, to best approximate the activity layers
+        try {
+            return mISystemUiProxy.screenshot(new Rect(), mDisplaySize.x, mDisplaySize.y, 0, 100000,
+                    false, mDisplayRotation).toBitmap();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error capturing snapshot", e);
+            return null;
+        }
+    }
+}
diff --git a/res/color/all_apps_tab_text.xml b/res/color/all_apps_tab_text.xml
new file mode 100644
index 0000000..f0c6310
--- /dev/null
+++ b/res/color/all_apps_tab_text.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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="?android:attr/colorAccent" android:state_selected="true"/>
+    <item android:color="?android:attr/textColorTertiary"/>
+</selector>
\ No newline at end of file
diff --git a/res/drawable/all_apps_handle_landscape.xml b/res/drawable/all_apps_handle_landscape.xml
new file mode 100644
index 0000000..23826ab
--- /dev/null
+++ b/res/drawable/all_apps_handle_landscape.xml
@@ -0,0 +1,37 @@
+<?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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="13dp"
+    android:height="13dp"
+    android:viewportWidth="13.0"
+    android:viewportHeight="13.0" >
+
+    <path
+        android:pathData="M2 8.5L6.5 4L11 8.5"
+        android:strokeColor="?attr/workspaceAmbientShadowColor"
+        android:strokeWidth="4"
+        android:strokeLineCap="round"
+        android:strokeLineJoin="round" />
+
+    <path
+        android:pathData="M2 8.5L6.5 4L11 8.5"
+        android:strokeColor="?attr/workspaceTextColor"
+        android:strokeWidth="2"
+        android:strokeLineCap="round"
+        android:strokeLineJoin="round" />
+
+</vector>
diff --git a/res/drawable/all_apps_handle_portrait.xml b/res/drawable/all_apps_handle_portrait.xml
new file mode 100644
index 0000000..75aa448
--- /dev/null
+++ b/res/drawable/all_apps_handle_portrait.xml
@@ -0,0 +1,34 @@
+<?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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="16dp"
+    android:height="13dp"
+    android:viewportWidth="16.0"
+    android:viewportHeight="13.0" >
+
+    <path
+        android:pathData="M2 6h12"
+        android:strokeColor="?attr/workspaceAmbientShadowColor"
+        android:strokeWidth="4"
+        android:strokeLineCap="round" />
+
+    <path
+        android:pathData="M2 6h12"
+        android:strokeColor="?attr/workspaceTextColor"
+        android:strokeWidth="2"
+        android:strokeLineCap="round" />
+</vector>
\ No newline at end of file
diff --git a/res/drawable/bg_notification_content.xml b/res/drawable/bg_notification_content.xml
new file mode 100644
index 0000000..cf129eb
--- /dev/null
+++ b/res/drawable/bg_notification_content.xml
@@ -0,0 +1,28 @@
+<?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.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="?attr/popupColorTertiary" />
+
+    <item android:height="3dp" android:top="0dp">
+        <shape>
+            <gradient
+                android:angle="270"
+                android:endColor="@android:color/transparent"
+                android:startColor="#33000000"
+                android:type="linear" />
+        </shape>
+    </item>
+</layer-list>
diff --git a/res/drawable/ic_install_no_shadow.xml b/res/drawable/ic_install_no_shadow.xml
new file mode 100644
index 0000000..ffce22a
--- /dev/null
+++ b/res/drawable/ic_install_no_shadow.xml
@@ -0,0 +1,27 @@
+<?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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+
+    <path
+        android:fillColor="?android:attr/textColorPrimary"
+        android:pathData="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z" />
+    <path
+        android:pathData="M0 0h24v24H0z" />
+</vector>
diff --git a/res/layout-land/launcher.xml b/res/layout-land/launcher.xml
index ac440fc..4ea32b4 100644
--- a/res/layout-land/launcher.xml
+++ b/res/layout-land/launcher.xml
@@ -42,6 +42,19 @@
             android:layout_gravity="center"
             launcher:pageIndicator="@id/page_indicator" />
 
+        <com.android.launcher3.pageindicators.PageIndicatorLandscape
+            android:id="@+id/page_indicator"
+            android:theme="@style/HomeScreenElementTheme"
+            android:layout_width="@dimen/dynamic_grid_min_page_indicator_size"
+            android:layout_height="@dimen/dynamic_grid_min_page_indicator_size"
+            android:layout_gravity="bottom|left">
+            <ImageView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:src="@drawable/all_apps_handle_landscape"
+                android:layout_gravity="center" />
+        </com.android.launcher3.pageindicators.PageIndicatorLandscape>
+
         <include layout="@layout/gradient_bg" />
 
         <!-- DO NOT CHANGE THE ID -->
@@ -60,25 +73,12 @@
             android:id="@+id/overview_panel"
             android:visibility="gone" />
 
-        <include layout="@layout/widgets_view"
-            android:id="@+id/widgets_view"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:visibility="invisible" />
-
         <include layout="@layout/all_apps"
             android:id="@+id/apps_view"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:visibility="invisible" />
 
-        <com.android.launcher3.pageindicators.PageIndicatorCaretLandscape
-            android:id="@+id/page_indicator"
-            android:theme="@style/HomeScreenElementTheme"
-            android:layout_width="@dimen/dynamic_grid_min_page_indicator_size"
-            android:layout_height="@dimen/dynamic_grid_min_page_indicator_size"
-            android:layout_gravity="bottom|left"/>
-
     </com.android.launcher3.dragndrop.DragLayer>
 
 </com.android.launcher3.LauncherRootView>
diff --git a/res/layout-port/launcher.xml b/res/layout-port/launcher.xml
index c41a6e3..f58a87e 100644
--- a/res/layout-port/launcher.xml
+++ b/res/layout-port/launcher.xml
@@ -66,12 +66,6 @@
             android:id="@+id/drop_target_bar"
             layout="@layout/drop_target_bar_horz" />
 
-        <include layout="@layout/widgets_view"
-            android:id="@+id/widgets_view"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:visibility="invisible" />
-
         <include layout="@layout/all_apps"
             android:id="@+id/apps_view"
             android:layout_width="match_parent"
diff --git a/res/layout-sw720dp/launcher.xml b/res/layout-sw720dp/launcher.xml
index 03e42bc..660d0ed 100644
--- a/res/layout-sw720dp/launcher.xml
+++ b/res/layout-sw720dp/launcher.xml
@@ -65,12 +65,6 @@
         <include layout="@layout/page_indicator"
                  android:id="@+id/page_indicator" />
 
-        <include layout="@layout/widgets_view"
-            android:id="@+id/widgets_view"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:visibility="invisible" />
-
         <include layout="@layout/all_apps"
             android:id="@+id/apps_view"
             android:layout_width="match_parent"
diff --git a/res/layout/all_apps.xml b/res/layout/all_apps.xml
index 39df2b1..832aaef 100644
--- a/res/layout/all_apps.xml
+++ b/res/layout/all_apps.xml
@@ -18,60 +18,68 @@
      will bake the left/right padding into that view's background itself. -->
 <com.android.launcher3.allapps.AllAppsContainerView
     xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:launcher="http://schemas.android.com/apk/res-auto"
     android:id="@+id/apps_view"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:orientation="vertical"
-    launcher:revealBackground="@drawable/round_rect_primary">
+    android:clipChildren="true"
+    android:clipToPadding="false"
+    android:focusable="true"
+    android:focusableInTouchMode="true"
+    android:saveEnabled="false" >
 
-    <View
-        android:id="@+id/reveal_view"
+    <include layout="@layout/all_apps_rv_layout" />
+
+    <include layout="@layout/all_apps_fast_scroller" />
+
+    <RelativeLayout
+        android:id="@+id/all_apps_header"
         android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:layout_gravity="center"
-        android:focusable="false"
-        android:visibility="invisible" />
+        android:layout_height="wrap_content"
+        android:clickable="true"
+        android:paddingTop="30dp"
+        android:layout_below="@id/search_container_all_apps" >
 
-
-    <com.android.launcher3.allapps.AllAppsRecyclerViewContainerView
-        android:id="@+id/main_content"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:layout_gravity="center"
-        android:focusable="true"
-        android:clipToPadding="false"
-        android:clipChildren="true"
-        android:focusableInTouchMode="true"
-        android:saveEnabled="false"
-        android:visibility="gone">
-
-        <!-- DO NOT CHANGE THE ID -->
-        <com.android.launcher3.allapps.AllAppsRecyclerView
-            android:id="@+id/apps_list_view"
-            android:layout_below="@id/search_container_all_apps"
+        <com.android.launcher3.allapps.PredictionRowView
+            android:id="@+id/header_content"
             android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:layout_gravity="center_horizontal|top"
-            android:clipToPadding="false"
-            android:overScrollMode="never"
-            android:descendantFocusability="afterDescendants"
-            android:focusable="true" />
+            android:layout_height="wrap_content"/>
 
-        <!-- Note: we are reusing/repurposing a system attribute for search layout, because of a
-         platform bug, which prevents using custom attributes in <include> tag -->
-        <include
-            layout="?android:attr/keyboardLayout"
-            android:id="@id/search_container_all_apps" />
+        <com.android.launcher3.views.SlidingTabStrip
+            android:id="@+id/tabs"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/all_apps_header_tab_height"
+            android:layout_below="@id/header_content"
+            android:orientation="horizontal">
+            <Button
+                android:id="@+id/tab_personal"
+                android:layout_width="0dp"
+                android:layout_height="match_parent"
+                android:layout_weight="1"
+                android:text="@string/all_apps_personal_tab"
+                android:textColor="@color/all_apps_tab_text"
+                android:background="?android:attr/selectableItemBackground"/>
+            <Button
+                android:id="@+id/tab_work"
+                android:layout_width="0dp"
+                android:layout_height="match_parent"
+                android:layout_weight="1"
+                android:text="@string/all_apps_work_tab"
+                android:textColor="@color/all_apps_tab_text"
+                android:background="?android:attr/selectableItemBackground"/>
+        </com.android.launcher3.views.SlidingTabStrip>
 
-        <include layout="@layout/all_apps_fast_scroller" />
+    </RelativeLayout>
 
-    </com.android.launcher3.allapps.AllAppsRecyclerViewContainerView>
+    <!-- Note: we are reusing/repurposing a system attribute for search layout, because of a
+     platform bug, which prevents using custom attributes in <include> tag -->
+    <include
+        android:id="@id/search_container_all_apps"
+        layout="?android:attr/keyboardLayout"/>
+
     <View
         android:id="@+id/nav_bar_bg"
-        android:background="?attr/allAppsNavBarScrimColor"
         android:layout_width="match_parent"
         android:layout_height="0dp"
-        android:layout_gravity="bottom"
-        android:focusable="false"  />
+        android:layout_alignParentBottom="true"
+        android:background="?attr/allAppsNavBarScrimColor" />
 </com.android.launcher3.allapps.AllAppsContainerView>
\ No newline at end of file
diff --git a/res/layout/all_apps_rv_layout.xml b/res/layout/all_apps_rv_layout.xml
new file mode 100644
index 0000000..3c19f8c
--- /dev/null
+++ b/res/layout/all_apps_rv_layout.xml
@@ -0,0 +1,26 @@
+<?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.
+  -->
+<com.android.launcher3.allapps.AllAppsRecyclerView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/apps_list_view"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_below="@id/search_container_all_apps"
+    android:clipToPadding="false"
+    android:descendantFocusability="afterDescendants"
+    android:focusable="true"
+    android:overScrollMode="never" />
diff --git a/res/layout/all_apps_tabs.xml b/res/layout/all_apps_tabs.xml
new file mode 100644
index 0000000..fa1d591
--- /dev/null
+++ b/res/layout/all_apps_tabs.xml
@@ -0,0 +1,34 @@
+<?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.
+  -->
+<com.android.launcher3.allapps.InterceptingViewPager
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/all_apps_tabs_view_pager"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_below="@id/search_container_all_apps"
+    android:layout_gravity="center_horizontal|top"
+    android:layout_marginTop="@dimen/all_apps_header_tab_height"
+    android:clipChildren="false"
+    android:clipToPadding="false"
+    android:descendantFocusability="afterDescendants"
+    android:paddingTop="30dp">
+
+    <include layout="@layout/all_apps_rv_layout" />
+
+    <include layout="@layout/all_apps_rv_layout" />
+
+</com.android.launcher3.allapps.InterceptingViewPager>
\ No newline at end of file
diff --git a/res/layout/app_widget_resize_frame.xml b/res/layout/app_widget_resize_frame.xml
index 874fecc..12561b6 100644
--- a/res/layout/app_widget_resize_frame.xml
+++ b/res/layout/app_widget_resize_frame.xml
@@ -21,42 +21,47 @@
     android:background="@drawable/widget_resize_shadow"
     android:foreground="@drawable/widget_resize_frame"
     android:foregroundTint="?attr/workspaceTextColor"
-    android:padding="0dp" >
+    android:padding="0dp">
 
-    <!-- Left -->
-    <ImageView
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="left|center_vertical"
-        android:layout_marginLeft="@dimen/widget_handle_margin"
-        android:src="@drawable/ic_widget_resize_handle"
-        android:tint="?attr/workspaceTextColor" />
+    <FrameLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" >
 
-    <!-- Top -->
-    <ImageView
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="top|center_horizontal"
-        android:layout_marginTop="@dimen/widget_handle_margin"
-        android:src="@drawable/ic_widget_resize_handle"
-        android:tint="?attr/workspaceTextColor" />
+        <!-- Left -->
+        <ImageView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="left|center_vertical"
+            android:layout_marginLeft="@dimen/widget_handle_margin"
+            android:src="@drawable/ic_widget_resize_handle"
+            android:tint="?attr/workspaceTextColor" />
 
-    <!-- Right -->
-    <ImageView
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="right|center_vertical"
-        android:layout_marginRight="@dimen/widget_handle_margin"
-        android:src="@drawable/ic_widget_resize_handle"
-        android:tint="?attr/workspaceTextColor" />
+        <!-- Top -->
+        <ImageView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="top|center_horizontal"
+            android:layout_marginTop="@dimen/widget_handle_margin"
+            android:src="@drawable/ic_widget_resize_handle"
+            android:tint="?attr/workspaceTextColor" />
 
-    <!-- Bottom -->
-    <ImageView
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="bottom|center_horizontal"
-        android:layout_marginBottom="@dimen/widget_handle_margin"
-        android:src="@drawable/ic_widget_resize_handle"
-        android:tint="?attr/workspaceTextColor" />
+        <!-- Right -->
+        <ImageView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="right|center_vertical"
+            android:layout_marginRight="@dimen/widget_handle_margin"
+            android:src="@drawable/ic_widget_resize_handle"
+            android:tint="?attr/workspaceTextColor" />
 
+        <!-- Bottom -->
+        <ImageView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="bottom|center_horizontal"
+            android:layout_marginBottom="@dimen/widget_handle_margin"
+            android:src="@drawable/ic_widget_resize_handle"
+            android:tint="?attr/workspaceTextColor" />
+
+    </FrameLayout>
 </com.android.launcher3.AppWidgetResizeFrame>
\ No newline at end of file
diff --git a/res/layout/deep_shortcut.xml b/res/layout/deep_shortcut.xml
index 4a2ad42..4a3db1f 100644
--- a/res/layout/deep_shortcut.xml
+++ b/res/layout/deep_shortcut.xml
@@ -35,6 +35,7 @@
         android:textColor="?android:attr/textColorPrimary"
         android:fontFamily="sans-serif"
         launcher:layoutHorizontal="true"
+        launcher:deferShadowGeneration="true"
         launcher:iconDisplay="shortcut_popup"
         launcher:iconSizeOverride="@dimen/deep_shortcut_icon_size" />
 
diff --git a/res/layout/gradient_bg.xml b/res/layout/gradient_bg.xml
index db448d7..6c6626c 100644
--- a/res/layout/gradient_bg.xml
+++ b/res/layout/gradient_bg.xml
@@ -20,5 +20,4 @@
     android:id="@+id/gradient_bg"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:visibility="gone"
     launcher:layout_ignoreInsets="true" />
\ No newline at end of file
diff --git a/res/layout/notification.xml b/res/layout/notification.xml
deleted file mode 100644
index 1eebb43..0000000
--- a/res/layout/notification.xml
+++ /dev/null
@@ -1,97 +0,0 @@
-<?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.
--->
-
-<com.android.launcher3.notification.NotificationItemView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/notification_view"
-    android:layout_width="@dimen/bg_popup_item_width"
-    android:layout_height="wrap_content"
-    android:theme="@style/PopupItem"
-    android:elevation="@dimen/deep_shortcuts_elevation">
-
-    <RelativeLayout
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:orientation="vertical"
-        android:clipChildren="false">
-
-        <View
-            android:id="@+id/gutter_top"
-            android:layout_width="match_parent"
-            android:layout_height="4dp"
-            android:theme="@style/PopupGutter"
-            android:visibility="gone" />
-
-        <FrameLayout
-            android:id="@+id/header"
-            android:layout_width="match_parent"
-            android:layout_height="@dimen/notification_header_height"
-            android:paddingStart="@dimen/notification_padding_start"
-            android:paddingEnd="@dimen/notification_padding_end"
-            android:background="?attr/popupColorPrimary"
-            android:elevation="@dimen/notification_elevation"
-            android:layout_below="@id/gutter_top" >
-            <TextView
-                android:id="@+id/notification_text"
-                android:layout_width="wrap_content"
-                android:layout_height="match_parent"
-                android:layout_gravity="start"
-                android:gravity="center_vertical"
-                android:text="@string/notifications_header"
-                android:textSize="@dimen/notification_header_text_size"
-                android:textColor="?android:attr/textColorPrimary" />
-            <TextView
-                android:id="@+id/notification_count"
-                android:layout_width="@dimen/notification_icon_size"
-                android:layout_height="match_parent"
-                android:layout_gravity="end"
-                android:gravity="center"
-                android:textSize="@dimen/notification_header_count_text_size"
-                android:fontFamily="sans-serif-medium"
-                android:textColor="?android:attr/textColorPrimary" />
-        </FrameLayout>
-
-        <include layout="@layout/notification_main"
-            android:id="@+id/main_view"
-            android:layout_width="match_parent"
-            android:layout_height="@dimen/notification_main_height"
-            android:layout_below="@id/header" />
-
-        <View
-            android:id="@+id/divider"
-            android:layout_width="match_parent"
-            android:layout_height="@dimen/popup_item_divider_height"
-            android:background="?attr/popupColorTertiary"
-            android:layout_below="@id/main_view"
-            android:visibility="gone" />
-
-        <include layout="@layout/notification_footer"
-            android:id="@+id/footer"
-            android:layout_width="match_parent"
-            android:layout_height="@dimen/notification_footer_height"
-            android:layout_below="@id/divider" />
-
-        <View
-            android:id="@+id/gutter_bottom"
-            android:layout_width="match_parent"
-            android:layout_height="4dp"
-            android:theme="@style/PopupGutter"
-            android:visibility="gone"
-            android:layout_below="@id/footer" />
-
-    </RelativeLayout>
-
-</com.android.launcher3.notification.NotificationItemView>
diff --git a/res/layout/notification_content.xml b/res/layout/notification_content.xml
new file mode 100644
index 0000000..d01be01
--- /dev/null
+++ b/res/layout/notification_content.xml
@@ -0,0 +1,134 @@
+<?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.
+-->
+
+<merge
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <!-- header -->
+    <FrameLayout
+        android:id="@+id/header"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/notification_header_height"
+        android:paddingEnd="@dimen/notification_padding_end"
+        android:paddingStart="@dimen/notification_padding_start">
+        <TextView
+            android:id="@+id/notification_text"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_gravity="start"
+            android:gravity="center_vertical"
+            android:text="@string/notifications_header"
+            android:textColor="?android:attr/textColorPrimary"
+            android:textSize="@dimen/notification_header_text_size" />
+        <TextView
+            android:id="@+id/notification_count"
+            android:layout_width="@dimen/notification_icon_size"
+            android:layout_height="match_parent"
+            android:layout_gravity="end"
+            android:fontFamily="sans-serif-medium"
+            android:gravity="center"
+            android:textColor="?android:attr/textColorPrimary"
+            android:textSize="@dimen/notification_header_count_text_size" />
+    </FrameLayout>
+
+    <!-- Main view -->
+    <com.android.launcher3.notification.NotificationMainView
+        android:id="@+id/main_view"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/notification_main_height"
+        android:background="@drawable/bg_notification_content"
+        android:focusable="true" >
+
+        <LinearLayout
+            android:id="@+id/text_and_background"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:background="?attr/popupColorPrimary"
+            android:gravity="center_vertical"
+            android:orientation="vertical"
+            android:paddingBottom="14dp"
+            android:paddingEnd="@dimen/notification_main_text_padding_end"
+            android:paddingStart="@dimen/notification_padding_start">
+            <TextView
+                android:id="@+id/title"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:ellipsize="end"
+                android:fontFamily="sans-serif"
+                android:lines="1"
+                android:textAlignment="viewStart"
+                android:textColor="?android:attr/textColorPrimary"
+                android:textSize="@dimen/notification_main_title_size" />
+
+            <TextView
+                android:id="@+id/text"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:ellipsize="end"
+                android:fontFamily="sans-serif"
+                android:lines="1"
+                android:textColor="?android:attr/textColorSecondary"
+                android:textSize="@dimen/notification_main_text_size" />
+        </LinearLayout>
+
+        <View
+            android:id="@+id/popup_item_icon"
+            android:layout_width="@dimen/notification_icon_size"
+            android:layout_height="@dimen/notification_icon_size"
+            android:layout_gravity="center_vertical|end"
+            android:layout_marginBottom="7dp"
+            android:layout_marginEnd="@dimen/notification_padding_end" />
+
+    </com.android.launcher3.notification.NotificationMainView>
+
+    <!-- Divider -->
+    <View
+        android:id="@+id/divider"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/popup_item_divider_height"
+        android:layout_below="@id/main_view"
+        android:background="?attr/popupColorTertiary" />
+
+    <!-- Footer -->
+    <com.android.launcher3.notification.NotificationFooterLayout
+        android:id="@+id/footer"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/notification_footer_height"
+        android:layout_gravity="center_vertical"
+        android:clipChildren="false">
+
+        <LinearLayout
+            android:id="@+id/icon_row"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:clipChildren="false"
+            android:clipToPadding="false"
+            android:gravity="end|center_vertical"
+            android:orientation="horizontal"
+            android:padding="@dimen/notification_footer_icon_row_padding"/>
+
+        <View
+            android:id="@+id/overflow"
+            android:layout_width="@dimen/horizontal_ellipsis_size"
+            android:layout_height="@dimen/horizontal_ellipsis_size"
+            android:layout_gravity="start|center_vertical"
+            android:layout_marginStart="@dimen/horizontal_ellipsis_offset"
+            android:background="@drawable/horizontal_ellipsis" />
+
+    </com.android.launcher3.notification.NotificationFooterLayout>
+</merge>
\ No newline at end of file
diff --git a/res/layout/notification_footer.xml b/res/layout/notification_footer.xml
deleted file mode 100644
index 86280e0..0000000
--- a/res/layout/notification_footer.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-<?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.
--->
-
-
-<com.android.launcher3.notification.NotificationFooterLayout
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:elevation="@dimen/notification_elevation"
-    android:clipChildren="false"
-    android:layout_gravity="center_vertical"
-    android:background="?attr/popupColorPrimary">
-
-    <LinearLayout
-        android:id="@+id/icon_row"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:orientation="horizontal"
-        android:gravity="end|center_vertical"
-        android:padding="@dimen/notification_footer_icon_row_padding"
-        android:clipToPadding="false"
-        android:clipChildren="false"/>
-
-    <View
-        android:id="@+id/overflow"
-        android:layout_width="@dimen/horizontal_ellipsis_size"
-        android:layout_height="@dimen/horizontal_ellipsis_size"
-        android:background="@drawable/horizontal_ellipsis"
-        android:layout_marginStart="@dimen/horizontal_ellipsis_offset"
-        android:layout_gravity="start|center_vertical" />
-
-</com.android.launcher3.notification.NotificationFooterLayout>
-
diff --git a/res/layout/notification_gutter.xml b/res/layout/notification_gutter.xml
new file mode 100644
index 0000000..10e7f7d
--- /dev/null
+++ b/res/layout/notification_gutter.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.
+-->
+<View
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="4dp"
+    android:layout_marginTop="4dp"
+    android:background="@drawable/bg_notification_content" />
\ No newline at end of file
diff --git a/res/layout/notification_main.xml b/res/layout/notification_main.xml
deleted file mode 100644
index f94face..0000000
--- a/res/layout/notification_main.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?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.
--->
-
-
-<com.android.launcher3.notification.NotificationMainView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:focusable="true"
-    android:elevation="@dimen/notification_elevation" >
-
-    <LinearLayout
-        android:id="@+id/text_and_background"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:orientation="vertical"
-        android:gravity="center_vertical"
-        android:background="?attr/popupColorPrimary"
-        android:paddingStart="@dimen/notification_padding_start"
-        android:paddingEnd="@dimen/notification_main_text_padding_end"
-        android:paddingBottom="14dp">
-        <TextView
-            android:id="@+id/title"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:textAlignment="viewStart"
-            android:fontFamily="sans-serif"
-            android:textSize="@dimen/notification_main_title_size"
-            android:textColor="?android:attr/textColorPrimary"
-            android:lines="1"
-            android:ellipsize="end" />
-
-        <TextView
-            android:id="@+id/text"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:fontFamily="sans-serif"
-            android:textSize="@dimen/notification_main_text_size"
-            android:textColor="?android:attr/textColorSecondary"
-            android:lines="1"
-            android:ellipsize="end" />
-    </LinearLayout>
-
-    <View
-        android:id="@+id/popup_item_icon"
-        android:layout_width="@dimen/notification_icon_size"
-        android:layout_height="@dimen/notification_icon_size"
-        android:layout_marginEnd="@dimen/notification_padding_end"
-        android:layout_marginBottom="7dp"
-        android:layout_gravity="center_vertical|end" />
-
-</com.android.launcher3.notification.NotificationMainView>
-
diff --git a/res/layout/overview_panel.xml b/res/layout/overview_panel.xml
index d1ac56c..c795b81 100644
--- a/res/layout/overview_panel.xml
+++ b/res/layout/overview_panel.xml
@@ -14,11 +14,9 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<LinearLayout
+<com.android.launcher3.uioverrides.OverviewPanel
       xmlns:android="http://schemas.android.com/apk/res/android"
-      xmlns:launcher="http://schemas.android.com/apk/res-auto"
       android:theme="@style/HomeScreenElementTheme"
-      launcher:layout_ignoreInsets="true"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:layout_gravity="center_horizontal|bottom"
@@ -73,4 +71,4 @@
         android:textColor="?attr/workspaceTextColor"
         android:textSize="12sp" />
 
-</LinearLayout>
\ No newline at end of file
+</com.android.launcher3.uioverrides.OverviewPanel>
\ No newline at end of file
diff --git a/res/layout/page_indicator.xml b/res/layout/page_indicator.xml
index 92f52d6..2df511b 100644
--- a/res/layout/page_indicator.xml
+++ b/res/layout/page_indicator.xml
@@ -14,7 +14,7 @@
      limitations under the License.
 -->
 
-<com.android.launcher3.pageindicators.PageIndicatorLineCaret
+<com.android.launcher3.pageindicators.PageIndicatorLine
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:theme="@style/HomeScreenElementTheme"
     android:layout_width="match_parent"
@@ -23,6 +23,7 @@
             android:id="@+id/all_apps_handle"
             android:layout_width="48dp"
             android:layout_height="@dimen/dynamic_grid_min_page_indicator_size"
+            android:src="@drawable/all_apps_handle_portrait"
             android:layout_gravity="top|center"
             android:scaleType="centerInside"/>
-</com.android.launcher3.pageindicators.PageIndicatorLineCaret>
+</com.android.launcher3.pageindicators.PageIndicatorLine>
diff --git a/res/layout/popup_container.xml b/res/layout/popup_container.xml
index 67db4a5..c737407 100644
--- a/res/layout/popup_container.xml
+++ b/res/layout/popup_container.xml
@@ -19,11 +19,8 @@
     android:id="@+id/deep_shortcuts_container"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
-    android:paddingTop="4dp"
-    android:paddingBottom="4dp"
+    android:background="?attr/popupColorPrimary"
     android:clipToPadding="false"
     android:clipChildren="false"
     android:elevation="@dimen/deep_shortcuts_elevation"
-    android:orientation="vertical">
-
-</com.android.launcher3.popup.PopupContainerWithArrow>
\ No newline at end of file
+    android:orientation="vertical" />
\ No newline at end of file
diff --git a/res/layout/shortcuts_item.xml b/res/layout/shortcuts_item.xml
deleted file mode 100644
index 7cd996d..0000000
--- a/res/layout/shortcuts_item.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<?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.
--->
-
-<com.android.launcher3.shortcuts.ShortcutsItemView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/shortcuts_view"
-    android:layout_width="@dimen/bg_popup_item_width"
-    android:layout_height="wrap_content"
-    android:elevation="@dimen/deep_shortcuts_elevation">
-
-    <LinearLayout
-        android:id="@+id/content"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:orientation="vertical">
-
-        <!-- The shortcuts header is added at runtime when necessary. -->
-
-        <LinearLayout
-            android:id="@+id/shortcuts"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:orientation="vertical" />
-    </LinearLayout>
-
-</com.android.launcher3.shortcuts.ShortcutsItemView>
diff --git a/res/layout/system_shortcut.xml b/res/layout/system_shortcut.xml
index 04f3d02..1888e22 100644
--- a/res/layout/system_shortcut.xml
+++ b/res/layout/system_shortcut.xml
@@ -34,6 +34,7 @@
         android:fontFamily="sans-serif"
         launcher:iconDisplay="shortcut_popup"
         launcher:layoutHorizontal="true"
+        launcher:deferShadowGeneration="true"
         android:focusable="false" />
 
     <View
diff --git a/res/layout/system_shortcut_icons.xml b/res/layout/system_shortcut_icons.xml
index 34d63e7..4daf469 100644
--- a/res/layout/system_shortcut_icons.xml
+++ b/res/layout/system_shortcut_icons.xml
@@ -22,6 +22,4 @@
     android:orientation="horizontal"
     android:gravity="end|center_vertical"
     android:background="?attr/popupColorSecondary"
-    android:elevation="1dp"
-    android:outlineProvider="none" />
-    <!-- We have elevation so this is drawn on top, but no outline provider to remove shadow -->
+    android:clipToPadding="true" />
diff --git a/res/layout/widgets_full_sheet.xml b/res/layout/widgets_full_sheet.xml
new file mode 100644
index 0000000..1535299
--- /dev/null
+++ b/res/layout/widgets_full_sheet.xml
@@ -0,0 +1,64 @@
+<?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.
+-->
+<com.android.launcher3.widget.WidgetsFullSheet
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:theme="?attr/widgetsTheme" >
+
+    <com.android.launcher3.graphics.GradientView
+        android:id="@+id/gradient_bg"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+    <FrameLayout
+        android:id="@+id/container"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="?android:attr/colorPrimary"
+        android:elevation="4dp">
+
+        <com.android.launcher3.widget.WidgetsRecyclerView
+            android:id="@+id/widgets_list_view"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:clipToPadding="false"
+            />
+
+        <!-- Fast scroller popup -->
+        <TextView
+            android:id="@+id/fast_scroller_popup"
+            style="@style/FastScrollerPopup"
+            android:layout_gravity="top|end"
+            android:layout_marginEnd="@dimen/fastscroll_popup_margin" />
+
+        <com.android.launcher3.views.RecyclerViewFastScroller
+            android:id="@+id/fast_scroller"
+            android:layout_width="@dimen/fastscroll_width"
+            android:layout_height="match_parent"
+            android:layout_gravity="end"
+            android:layout_marginEnd="@dimen/fastscroll_end_margin" />
+
+        <View
+            android:id="@+id/nav_bar_bg"
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:layout_gravity="bottom"
+            android:background="?attr/allAppsNavBarScrimColor"
+            android:focusable="false"  />
+    </FrameLayout>
+</com.android.launcher3.widget.WidgetsFullSheet>
\ No newline at end of file
diff --git a/res/layout/widgets_view.xml b/res/layout/widgets_view.xml
deleted file mode 100644
index 4f3c7c8..0000000
--- a/res/layout/widgets_view.xml
+++ /dev/null
@@ -1,73 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2015 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.
--->
-<!-- The top and bottom paddings are defined in this container, but since we want
-     the list view to span the full width (for touch interception purposes), we
-     will bake the left/right padding into that view's background itself. -->
-<com.android.launcher3.widget.WidgetsContainerView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:launcher="http://schemas.android.com/apk/res-auto"
-    android:id="@+id/widgets_view"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:descendantFocusability="afterDescendants"
-    android:theme="?attr/widgetsTheme"
-    launcher:revealBackground="@drawable/round_rect_primary">
-
-    <View
-        android:id="@+id/reveal_view"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:layout_gravity="center"
-        android:elevation="2dp"
-        android:focusable="false"
-        android:visibility="invisible" />
-
-    <FrameLayout
-        android:id="@+id/main_content"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:layout_gravity="center"
-        android:elevation="15dp"
-        android:visibility="gone">
-
-        <com.android.launcher3.widget.WidgetsRecyclerView
-            android:id="@+id/widgets_list_view"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent" />
-
-        <!-- Fast scroller popup -->
-        <TextView
-            android:id="@+id/fast_scroller_popup"
-            style="@style/FastScrollerPopup"
-            android:layout_gravity="top|end"
-            android:layout_marginEnd="@dimen/fastscroll_popup_margin" />
-
-        <ProgressBar
-            android:id="@+id/loader"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="center" />
-
-        <com.android.launcher3.views.RecyclerViewFastScroller
-            android:id="@+id/fast_scroller"
-            android:layout_width="@dimen/fastscroll_width"
-            android:layout_height="match_parent"
-            android:layout_gravity="end"
-            android:layout_marginEnd="@dimen/fastscroll_end_margin" />
-
-    </FrameLayout>
-
-</com.android.launcher3.widget.WidgetsContainerView>
\ No newline at end of file
diff --git a/res/layout/zzz_weight_watcher.xml b/res/layout/zzz_weight_watcher.xml
deleted file mode 100644
index 07fd39e..0000000
--- a/res/layout/zzz_weight_watcher.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<com.android.launcher3.testing.WeightWatcher xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent" />
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index d71b4c7..5a776a6 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Verwyder"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Deïnstalleer"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"Programinligting"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"Installeer"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"installeer kortpaaie"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Laat \'n program toe om kortpaaie by te voeg sonder gebruikerinmenging."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"lees Tuis-instellings en -kortpaaie"</string>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index 293b452..5225dba 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"አስወግድ"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"አራግፍ"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"የመተግበሪያ መረጃ"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"ጫን"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"አቋራጮችን ይጭናል"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"መተግበሪያው ያለተጠቃሚ ጣልቃ ገብነት አቋራጭ እንዲያክል ያስችለዋል።"</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"የመነሻ ቅንብሮች እና አቋራጮችን ያነባል"</string>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 6447bf0..5f2d58d 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"إزالة"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"إلغاء التثبيت"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"معلومات عن التطبيق"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"تثبيت"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"تثبيت اختصارات"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"للسماح لتطبيق ما بإضافة اختصارات بدون تدخل المستخدم."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"قراءة إعدادات واختصارات الشاشة الرئيسية"</string>
diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml
index 18f87bc..dddb6fa 100644
--- a/res/values-az/strings.xml
+++ b/res/values-az/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Silin"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Sistemdən sil"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"Tətbiq məlumatı"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"Quraşdırın"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"qısayolları quraşdır"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Tətbiqə istifadəçi müdaxiləsi olmadan qısayolları əlavə etməyə icazə verir."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"Əsas Səhifə ayarlarını və qısayolları oxuyun"</string>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index 777512a..4fa86d2 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Ukloni"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Deinstaliraj"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"Inform. o aplikaciji"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"Instaliraj"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"instaliranje prečica"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Dozvoljava aplikaciji da dodaje prečice bez intervencije korisnika."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"čitanje podešavanja i prečica na početnom ekranu"</string>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index 0a31ddf..1d7797d 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Выдаліць"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Выдаліць"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"Звесткі пра праграмы"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"Усталяваць"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"усталёўваць ярлыкі"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Дазваляе праграмам дадаваць ярлыкі без умяшання карыстальніка."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"счытваць налады і ярлыкі на Галоўнай старонцы"</string>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index 3e05e0b..b429492 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Премахване"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Деинсталиране"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"Данни за прилож."</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"Инсталиране"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"инсталиране на преки пътища"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Разрешава на приложението да добавя преки пътища без намеса на потребителя."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"четене на настройките и преките пътища в Начало"</string>
diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml
index 471602f..897a8db 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/strings.xml
@@ -35,10 +35,10 @@
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%2$d উচ্চতা অনুযায়ী %1$d প্রস্থ"</string>
     <string name="add_item_request_drag_hint" msgid="5899764264480397019">"নিজে যোগ করতে টাচ করে ধরে রাখুন"</string>
     <string name="place_automatically" msgid="8064208734425456485">"স্বয়ংক্রিয়ভাবে যোগ করুন"</string>
-    <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"অ্যাপ অনুসন্ধান করুন"</string>
+    <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"অ্যাপ খুঁজুন"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"অ্যাপ লোড হচ্ছে…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" এর সাথে মেলে এমন কোনো অ্যাপ পাওয়া যায়নি"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"আরো অ্যাপ্লিকেশানের জন্য অনুসন্ধান করুন"</string>
+    <string name="all_apps_search_market_message" msgid="1366263386197059176">"আরও অ্যাপ্লিকেশানের জন্য খুঁজুন"</string>
     <string name="notifications_header" msgid="1404149926117359025">"বিজ্ঞপ্তি"</string>
     <string name="out_of_space" msgid="4691004494942118364">"এই হোম স্ক্রীনে আর কোনো জায়গা নেই৷"</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"পছন্দসই ট্রে-তে আর কোনো জায়গা নেই"</string>
@@ -46,7 +46,8 @@
     <string name="all_apps_home_button_label" msgid="252062713717058851">"হোম"</string>
     <string name="remove_drop_target_label" msgid="7812859488053230776">"সরান"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"আনইনস্টল করুন"</string>
-    <string name="app_info_drop_target_label" msgid="692894985365717661">"অ্যাপ্লিকেশানের তথ্য"</string>
+    <string name="app_info_drop_target_label" msgid="692894985365717661">"অ্যাপের তথ্য"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"ইনস্টল করুন"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"শর্টকাটগুলি ইনস্টল করে"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"একটি অ্যাপ্লিকেশানকে ব্যবহারকারীর হস্তক্ষেপ ছাড়াই শর্টকাটগুলি যোগ করার অনুমতি দেয়৷"</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"হোম সেটিংস এবং শর্টকাটগুলি পড়ে"</string>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index 49c862a..b062b88 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Ukloni"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Deinstaliraj"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"Informacije o aplikaciji"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"Instaliraj"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"instaliraj prečice"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Dopušta aplikaciji dodavanje prečica bez posredovanja korisnika."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"čitaj postavke na početnom ekranu i prečice"</string>
@@ -64,7 +65,7 @@
     <string name="workspace_new_page" msgid="257366611030256142">"Nova stranica početnog ekrana"</string>
     <string name="folder_opened" msgid="94695026776264709">"Folder je otvoren, (š) <xliff:g id="WIDTH">%1$d</xliff:g> (v) <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Dodirnite da zatvorite folder"</string>
-    <string name="folder_tap_to_rename" msgid="4017685068016979677">"Dodirnite da sačuvate promjenu imena"</string>
+    <string name="folder_tap_to_rename" msgid="4017685068016979677">"Dodirnite da sačuvate promjenu naziva"</string>
     <string name="folder_closed" msgid="4100806530910930934">"Folder je zatvoren"</string>
     <string name="folder_renamed" msgid="1794088362165669656">"Ime foldera je promijenjeno u <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format" msgid="6629239338071103179">"Folder: <xliff:g id="NAME">%1$s</xliff:g>"</string>
@@ -82,7 +83,7 @@
     <string name="title_missing_notification_access" msgid="7503287056163941064">"Potreban je pristup obavještenjima"</string>
     <string name="msg_missing_notification_access" msgid="281113995110910548">"Za prikaz tačaka obavještenja, uključite obavještenja za aplikacije za aplikaciju <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="title_change_settings" msgid="1376365968844349552">"Promijeni postavke"</string>
-    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Dodajte ikonu na početni ekran"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Dodaj ikonu na početni ekran"</string>
     <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Za nove aplikacije"</string>
     <string name="icon_shape_override_label" msgid="2977264953998281004">"Promjena oblika ikona"</string>
     <string name="icon_shape_system_default" msgid="1709762974822753030">"Koristite sistemski zadano"</string>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index b1a1f85..008531a 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Suprimeix"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Desinstal·la"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"Dades de l\'aplicació"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"Instal·la"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"instal·la dreceres"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Permet que una aplicació afegeixi dreceres sense la intervenció de l\'usuari."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"llegeix la configuració i les dreceres de la pantalla d\'inici"</string>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index e6dee3c..7cd71c2 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -46,7 +46,8 @@
     <string name="all_apps_home_button_label" msgid="252062713717058851">"Plocha"</string>
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Odstranit"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Odinstalovat"</string>
-    <string name="app_info_drop_target_label" msgid="692894985365717661">"Informace o aplikaci"</string>
+    <string name="app_info_drop_target_label" msgid="692894985365717661">"O aplikaci"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"Nainstalovat"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"instalace zástupce"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Umožňuje aplikaci přidat zástupce bez zásahu uživatele."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"čtení nastavení a odkazů plochy"</string>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 653105e..9a91bc4 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -46,7 +46,8 @@
     <string name="all_apps_home_button_label" msgid="252062713717058851">"Startskærm"</string>
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Fjern"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Afinstaller"</string>
-    <string name="app_info_drop_target_label" msgid="692894985365717661">"Oplysninger om appen"</string>
+    <string name="app_info_drop_target_label" msgid="692894985365717661">"Appinfo"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"Installer"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"installere genveje"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Tillader, at en app tilføjer genveje uden brugerens indgriben."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"læs indstillinger og genveje for startskærmen"</string>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 1c4cd7a..6bc0875 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Entfernen"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Deinstallieren"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"App-Details"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"Installieren"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"Verknüpfungen installieren"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Ermöglicht einer App das Hinzufügen von Verknüpfungen ohne Eingreifen des Nutzers"</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"Einstellungen und Verknüpfungen auf dem Startbildschirm lesen"</string>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index 25c8866..cdb255d 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Κατάργηση"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Απεγκατάσταση"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"Πληροφορίες εφαρμογής"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"Εγκατάσταση"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"εγκατάσταση συντομεύσεων"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Επιτρέπει σε μια εφαρμογή την προσθήκη συντομεύσεων χωρίς την παρέμβαση του χρήστη."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"ανάγνωση ρυθμίσεων και συντομεύσεων αρχικής οθόνης"</string>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index f3f10cc..afd6757 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Remove"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Uninstall"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"App info"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"Install"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"install shortcuts"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Allows an app to add shortcuts without user intervention."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"read Home settings and shortcuts"</string>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index f3f10cc..afd6757 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Remove"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Uninstall"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"App info"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"Install"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"install shortcuts"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Allows an app to add shortcuts without user intervention."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"read Home settings and shortcuts"</string>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index f3f10cc..afd6757 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Remove"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Uninstall"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"App info"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"Install"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"install shortcuts"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Allows an app to add shortcuts without user intervention."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"read Home settings and shortcuts"</string>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index f60e186..05f1e06 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Quitar"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Desinstalar"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"Información de app"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"Instalar"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"instalar accesos directos"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Permite que una aplicación agregue accesos directos sin que el usuario intervenga."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"leer configuración y accesos directos de la pantalla principal"</string>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 3ab41ca..e7e1cf8 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Quitar"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Desinstalar"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"Datos de aplicación"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"Instalar"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"instalar accesos directos"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Permite que una aplicación añada accesos directos sin intervención del usuario."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"leer información de accesos directos y de ajustes de la pantalla de inicio"</string>
@@ -83,7 +84,7 @@
     <string name="msg_missing_notification_access" msgid="281113995110910548">"Para mostrar burbujas de notificación, activa las notificaciones de <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="title_change_settings" msgid="1376365968844349552">"Cambiar ajustes"</string>
     <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Añadir icono a la pantalla de inicio"</string>
-    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Para nuevas aplicaciones"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Para aplicaciones nuevas"</string>
     <string name="icon_shape_override_label" msgid="2977264953998281004">"Cambiar forma de los iconos"</string>
     <string name="icon_shape_system_default" msgid="1709762974822753030">"Usar opción predeterminada del sistema"</string>
     <string name="icon_shape_square" msgid="633575066111622774">"Cuadrado"</string>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index 9d9991d..ccc85a3 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Eemalda"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Desinstalli"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"Rakenduse teave"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"Installimine"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"installi otseteed"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Võimaldab rakendusel lisada otseteid kasutaja sekkumiseta."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"loe avaekraani seadeid ja otseteid"</string>
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index b6082fad..68478de 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Kendu"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Desinstalatu"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"Aplikazioaren datuak"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"Instalatu"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"Instalatu lasterbideak"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Erabiltzaileak ezer egin gabe lasterbideak gehitzea baimentzen die aplikazioei."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"Irakurri hasierako ezarpenak eta lasterbideak"</string>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index 8fe874b..8051b85 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"برداشتن"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"حذف نصب"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"اطلاعات برنامه"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"نصب"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"نصب میان‌برها"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"به برنامه اجازه می‌دهد میان‌برها را بدون دخالت کاربر اضافه کند."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"خواندن تنظیمات و میان‌برهای صفحه اصلی"</string>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index 016dbbc..5e05bbe 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Poista"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Poista asennus"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"Sovelluksen tiedot"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"Asenna"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"asenna pikakuvakkeita"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Antaa sovelluksen lisätä pikakuvakkeita itsenäisesti ilman käyttäjän valintaa."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"lue aloitusruudun asetuksia ja pikakuvakkeita"</string>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index e57433d..da403a0 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -24,7 +24,7 @@
     <string name="work_folder_name" msgid="3753320833950115786">"Travail"</string>
     <string name="activity_not_found" msgid="8071924732094499514">"L\'application n\'est pas installée."</string>
     <string name="activity_not_available" msgid="7456344436509528827">"Application indisponible"</string>
-    <string name="safemode_shortcut_error" msgid="9160126848219158407">"L\'application téléchargée est désactivée en mode sécurisé."</string>
+    <string name="safemode_shortcut_error" msgid="9160126848219158407">"L\'application téléchargée est désactivée en mode sans échec."</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widgets désactivés en mode sans échec"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Le raccourci n\'est pas disponible"</string>
     <string name="home_screen" msgid="806512411299847073">"Écran d\'accueil"</string>
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Supprimer"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Désinstaller"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"Détails de l\'appli"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"Installer"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"installer des raccourcis"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Permet à une application d\'ajouter des raccourcis sans l\'intervention de l\'utilisateur."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"lire les paramètres et les raccourcis de la page d\'accueil"</string>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index dde4392..bc98cd2 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Supprimer"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Désinstaller"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"Infos sur l\'appli"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"Installer"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"installer des raccourcis"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Permettre à une application d\'ajouter des raccourcis sans l\'intervention de l\'utilisateur"</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"lire les paramètres et les raccourcis de l\'écran d\'accueil"</string>
diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml
index d55514d..54f9604 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Eliminar"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Desinstalar"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"Info. da aplicación"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"Instalar"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"instalar atallos"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Permite a unha aplicación engadir atallos sen intervención do usuario."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"ler a configuración e os atallos da pantalla de inicio"</string>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index 7096a3e..9734781 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -39,7 +39,7 @@
     <string name="all_apps_loading_message" msgid="5813968043155271636">"ઍપ્લિકેશનો લોડ કરી રહ્યું છે…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\"થી મેળ ખાતી કોઈ ઍપ્લિકેશનો મળી નથી"</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"વધુ ઍપ્લિકેશનો શોધો"</string>
-    <string name="notifications_header" msgid="1404149926117359025">"સૂચનાઓ"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"નોટિફિકેશનો"</string>
     <string name="out_of_space" msgid="4691004494942118364">"આ હોમ સ્ક્રીન પર વધુ જગ્યા નથી."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"મનપસંદ ટ્રે પર વધુ જગ્યા નથી"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"ઍપ્લિકેશનોની સૂચિ"</string>
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"દૂર કરો"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"અનઇન્સ્ટોલ કરો"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"ઍપ્લિકેશન માહિતી"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"ઇન્સ્ટૉલ કરો"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"શોર્ટકટ્સ ઇન્સ્ટોલ કરો"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"એપ્લિકેશનને વપરાશકર્તા હસ્તક્ષેપ વગર શોર્ટકટ્સ ઉમેરવાની મંજૂરી આપે છે."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"હોમ સેટિંગ્સ અને શોર્ટકટ્સ વાંચો"</string>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index f1ee0f2..2387404 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -28,31 +28,32 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"विजेट सुरक्षित मोड में अक्षम हैं"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"शॉर्टकट उपलब्ध नहीं है"</string>
     <string name="home_screen" msgid="806512411299847073">"होम स्क्रीन"</string>
-    <string name="custom_actions" msgid="3747508247759093328">"कस्टम कार्रवाई"</string>
-    <string name="long_press_widget_to_add" msgid="7699152356777458215">"विजेट को चुनने के लिए स्‍पर्श करके रखें."</string>
-    <string name="long_accessible_way_to_add" msgid="4289502106628154155">"कोई विजेट चुनने के लिए डबल टैप करके रखें या कस्‍टम कार्रवाइयां चुनें."</string>
+    <string name="custom_actions" msgid="3747508247759093328">"कस्टम कार्रवाइयां"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"विजेट को चुनने के लिए दबाकर रखें"</string>
+    <string name="long_accessible_way_to_add" msgid="4289502106628154155">"कोई विजेट चुनने के लिए दो बार छूएं और दबाये रखें या अपने मुताबिक कार्रवाइयां चुनें."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d चौड़ाई गुणा %2$d ऊंचाई"</string>
-    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"मैन्युअल रूप से जोड़ने के लिए स्पर्श करके रखें"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"खुद जोड़ने के लिए दबाकर रखें"</string>
     <string name="place_automatically" msgid="8064208734425456485">"अपने आप जोड़ें"</string>
-    <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"ऐप्लिकेशन खोजें"</string>
+    <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"ऐप सर्च करें"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"ऐप्लिकेशन लोड हो रहे हैं…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" से मिलता-जुलता कोई ऐप्लिकेशन नहीं मिला"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"अधिक ऐप्लिकेशन खोजें"</string>
-    <string name="notifications_header" msgid="1404149926117359025">"नोटिफ़िकेशन"</string>
-    <string name="out_of_space" msgid="4691004494942118364">"इस होम स्‍क्रीन पर स्थान शेष नहीं है."</string>
-    <string name="hotseat_out_of_space" msgid="7448809638125333693">"पसंदीदा ट्रे में और स्थान नहीं है"</string>
+    <string name="all_apps_search_market_message" msgid="1366263386197059176">"और ऐप सर्च करें"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"सूचनाएं"</string>
+    <string name="out_of_space" msgid="4691004494942118364">"इस होम स्‍क्रीन पर जगह नहीं बची है"</string>
+    <string name="hotseat_out_of_space" msgid="7448809638125333693">"पसंदीदा ट्रे में और जगह नहीं है"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"ऐप्लिकेशन सूची"</string>
-    <string name="all_apps_home_button_label" msgid="252062713717058851">"होम"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"होम पेज"</string>
     <string name="remove_drop_target_label" msgid="7812859488053230776">"निकालें"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"अनइंस्टॉल करें"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"ऐप की जानकारी"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"इंस्‍टॉल करें"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"शॉर्टकट इंस्‍टॉल करें"</string>
-    <string name="permdesc_install_shortcut" msgid="923466509822011139">"ऐप्लिकेशन को उपयोगकर्ता के हस्‍तक्षेप के बिना शॉर्टकट जोड़ने देती है."</string>
-    <string name="permlab_read_settings" msgid="1941457408239617576">"होम सेटिंग और शॉर्टकट पढ़ें"</string>
-    <string name="permdesc_read_settings" msgid="5833423719057558387">"ऐप्लिकेशन को होम में सेटिंग और शॉर्टकट पढ़ने देती है."</string>
-    <string name="permlab_write_settings" msgid="3574213698004620587">"होम सेटिंग और शॉर्टकट लिखें"</string>
-    <string name="permdesc_write_settings" msgid="5440712911516509985">"ऐप्लिकेशन को होम में सेटिंग और शॉर्टकट बदलने देती है."</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"ऐप को उपयोगकर्ता के हस्‍तक्षेप के बिना शॉर्टकट जोड़ने देती है."</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"होम पेज की सेटिंग और शॉर्टकट पढ़ें"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"ऐप्लिकेशन को होम पेज में सेटिंग और शॉर्टकट पढ़ने देती है."</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"होम पेज की सेटिंग और शॉर्टकट लिखें"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"ऐप्लिकेशन को होम पेज में सेटिंग और शॉर्टकट बदलने देती है."</string>
     <string name="msg_no_phone_permission" msgid="9208659281529857371">"<xliff:g id="APP_NAME">%1$s</xliff:g> को फ़ोन कॉल करने की अनुमति नहीं है"</string>
     <string name="gadget_error_text" msgid="6081085226050792095">"विजेट लोड करने में समस्‍या"</string>
     <string name="gadget_setup_text" msgid="8274003207686040488">"सेटअप"</string>
@@ -61,7 +62,7 @@
     <string name="disabled_app_label" msgid="6673129024321402780">"<xliff:g id="APP_NAME">%1$s</xliff:g> अक्षम है"</string>
     <string name="default_scroll_format" msgid="7475544710230993317">"पेज %2$d में से %1$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"होम स्क्रीन %2$d में से %1$d"</string>
-    <string name="workspace_new_page" msgid="257366611030256142">"नया होम स्‍क्रीन पृष्‍ठ"</string>
+    <string name="workspace_new_page" msgid="257366611030256142">"नया होम स्‍क्रीन पेज"</string>
     <string name="folder_opened" msgid="94695026776264709">"फ़ोल्डर खोला गया, <xliff:g id="WIDTH">%1$d</xliff:g> गुणा <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"फ़ोल्डर बंद करने के लिए टैप करें"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"नाम बदलना सहेजने के लिए टैप करें"</string>
@@ -70,32 +71,32 @@
     <string name="folder_name_format" msgid="6629239338071103179">"फ़ोल्डर: <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="widget_button_text" msgid="2880537293434387943">"शॉर्टकट"</string>
     <string name="wallpaper_button_text" msgid="8404103075899945851">"वॉलपेपर"</string>
-    <string name="settings_button_text" msgid="8873672322605444408">"होम सेटिंग"</string>
+    <string name="settings_button_text" msgid="8873672322605444408">"होम पेज की सेटिंग"</string>
     <string name="msg_disabled_by_admin" msgid="6898038085516271325">"आपके व्यवस्थापक द्वारा अक्षम"</string>
     <string name="accessibility_action_overview" msgid="6257665857640347026">"खास जानकारी"</string>
     <string name="allow_rotation_title" msgid="7728578836261442095">"होमस्क्रीन घुमाने की अनुमति दें"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"फ़ोन घुुमाए जाने पर"</string>
-    <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"वर्तमान प्रदर्शन सेटिंग घुमाने की अनुमति नहीं देती"</string>
-    <string name="icon_badging_title" msgid="874121399231955394">"नोटिफ़िकेशन बिंदु"</string>
+    <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"इस डिसप्ले सेटिंग में रोटेशन (स्क्रीन पर सामग्री को घुमाकर देखने) की सुविधा काम नहीं करती"</string>
+    <string name="icon_badging_title" msgid="874121399231955394">"सूचना बिंदु"</string>
     <string name="icon_badging_desc_on" msgid="2627952638544674079">"चालू"</string>
     <string name="icon_badging_desc_off" msgid="5503319969924580241">"बंद"</string>
-    <string name="title_missing_notification_access" msgid="7503287056163941064">"नोटिफ़िकेशन एक्‍सेस ज़रूरी है"</string>
-    <string name="msg_missing_notification_access" msgid="281113995110910548">"नोटिफ़िकेशन बिंदु दिखाने के लिए, <xliff:g id="NAME">%1$s</xliff:g> के ऐप्लिकेशन नोटिफ़िकेशन चालू करें"</string>
+    <string name="title_missing_notification_access" msgid="7503287056163941064">"सूचना के एक्सेस की ज़रूरत है"</string>
+    <string name="msg_missing_notification_access" msgid="281113995110910548">"सूचना बिंदु दिखाने के लिए, <xliff:g id="NAME">%1$s</xliff:g> के ऐप्लिकेशन सूचना चालू करें"</string>
     <string name="title_change_settings" msgid="1376365968844349552">"सेटिंग बदलें"</string>
-    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"होम स्क्रीन में आइकन जोड़ें"</string>
-    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"नए ऐप्लिकेशन के लिए"</string>
-    <string name="icon_shape_override_label" msgid="2977264953998281004">"आइकन का आकार बदलें"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"होम स्क्रीन में आइकॉन जोड़ें"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"नए ऐप के लिए"</string>
+    <string name="icon_shape_override_label" msgid="2977264953998281004">"आइकॉन का आकार बदलें"</string>
     <string name="icon_shape_system_default" msgid="1709762974822753030">"सिस्टम डिफ़ॉल्ट का उपयोग करें"</string>
     <string name="icon_shape_square" msgid="633575066111622774">"वर्ग"</string>
     <string name="icon_shape_squircle" msgid="5658049910802669495">"गोल कोनों वाला वर्ग"</string>
     <string name="icon_shape_circle" msgid="6550072265930144217">"मंडली"</string>
     <string name="icon_shape_teardrop" msgid="4525869388200835463">"आंसू की बूंद"</string>
-    <string name="icon_shape_override_progress" msgid="3461735694970239908">"आइकन के आकार में बदलाव लागू किए जा रहे हैं"</string>
+    <string name="icon_shape_override_progress" msgid="3461735694970239908">"आइकॉन के आकार में बदलाव किए जा रहे हैं"</string>
     <string name="package_state_unknown" msgid="7592128424511031410">"अज्ञात"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"निकालें"</string>
-    <string name="abandoned_search" msgid="891119232568284442">"खोजें"</string>
+    <string name="abandoned_search" msgid="891119232568284442">"सर्च करें"</string>
     <string name="abandoned_promises_title" msgid="7096178467971716750">"यह ऐप्स इंस्टॉल नहीं है"</string>
-    <string name="abandoned_promise_explanation" msgid="3990027586878167529">"इस आइकन का ऐप्स इंस्टॉल नहीं है. आप उसे निकाल सकते हैं या ऐप्स की खोज करके उसे मैन्युअल रूप से इंस्टॉल कर सकते हैं."</string>
+    <string name="abandoned_promise_explanation" msgid="3990027586878167529">"इस आइकॉन का ऐप इंस्टॉल नहीं है. आप उसे निकाल सकते हैं या ऐप को खोज कर उसे मैन्युअल रूप से इंस्टॉल कर सकते हैं."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> डाउनलोड हो रहा है, <xliff:g id="PROGRESS">%2$s</xliff:g> पूर्ण"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> के इंस्टॉल होने की प्रतीक्षा की जा रही है"</string>
     <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> विजेट"</string>
@@ -125,7 +126,7 @@
     <string name="widget_resized" msgid="9130327887929620">"विजेट का आकार बदलकर उसकी चौड़ाई <xliff:g id="NUMBER_0">%1$s</xliff:g> और ऊंचाई <xliff:g id="NUMBER_1">%2$s</xliff:g> कर दी गई"</string>
     <string name="action_deep_shortcut" msgid="2864038805849372848">"शॉर्टकट"</string>
     <string name="shortcuts_menu_description" msgid="406159963824238648">"<xliff:g id="APP_NAME">%2$s</xliff:g> के लिए <xliff:g id="NUMBER_OF_SHORTCUTS">%1$d</xliff:g> शॉर्टकट"</string>
-    <string name="shortcuts_menu_with_notifications_description" msgid="8985659504915468840">"<xliff:g id="APP_NAME">%3$s</xliff:g> के लिए <xliff:g id="NUMBER_OF_SHORTCUTS">%1$d</xliff:g> शॉर्टकट और <xliff:g id="NUMBER_OF_NOTIFICATIONS">%2$d</xliff:g> नोटिफ़िकेशन हैं"</string>
+    <string name="shortcuts_menu_with_notifications_description" msgid="8985659504915468840">"<xliff:g id="APP_NAME">%3$s</xliff:g> के लिए <xliff:g id="NUMBER_OF_SHORTCUTS">%1$d</xliff:g> शॉर्टकट और <xliff:g id="NUMBER_OF_NOTIFICATIONS">%2$d</xliff:g> सूचनाएं हैं"</string>
     <string name="action_dismiss_notification" msgid="5909461085055959187">"खारिज करें"</string>
-    <string name="notification_dismissed" msgid="6002233469409822874">"नोटिफ़िकेशन को खारिज किया गया"</string>
+    <string name="notification_dismissed" msgid="6002233469409822874">"सूचना को खारिज किया गया"</string>
 </resources>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index aaae258..f533015 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Ukloni"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Deinstaliraj"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"Info o aplikaciji"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"Instaliraj"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"instaliranje prečaca"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Aplikaciji omogućuje dodavanje prečaca bez intervencije korisnika."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"čitanje postavki početnog zaslona i prečaca"</string>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index 2da64b0..e38da35 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Törlés"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Eltávolítás"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"Alkalmazásinformáció"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"Telepítés"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"parancsikonok telepítése"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Lehetővé teszi egy alkalmazás számára, hogy felhasználói beavatkozás nélkül adjon hozzá parancsikonokat."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"Főoldal beállításainak és parancsikonjainak beolvasása"</string>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
index 2b55e96..4901dc5 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Հեռացնել"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Հեռացնել"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"Հավելվածի տվյալներ"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"Տեղադրել"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"տեղադրել դյուրանցումներ"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Ծրագրին թույլ է տալիս ավելացնել դյուրանցումներ՝ առանց օգտագործողի միջամտության:"</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"կարդալ հիմնաէջի կարգավորումներն ու դյուրանցումները"</string>
@@ -76,11 +77,11 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"Թույլ տալ հիմնական էկրանի պտտումը"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Հեռախոսը պտտելու դեպքում"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Ցուցադրման ընթացիկ կարգավորումներն արգելում են պտտումը"</string>
-    <string name="icon_badging_title" msgid="874121399231955394">"Ծանուցման կետեր"</string>
+    <string name="icon_badging_title" msgid="874121399231955394">"Ծանուցումների կետիկներ"</string>
     <string name="icon_badging_desc_on" msgid="2627952638544674079">"Միացված է"</string>
     <string name="icon_badging_desc_off" msgid="5503319969924580241">"Անջատված է"</string>
     <string name="title_missing_notification_access" msgid="7503287056163941064">"Անհրաժեշտ է ծանուցման թույլտվություն"</string>
-    <string name="msg_missing_notification_access" msgid="281113995110910548">"Ծանուցման կետերը ցուցադրելու համար միացրեք հավելվածի ծանուցումները <xliff:g id="NAME">%1$s</xliff:g>-ի համար"</string>
+    <string name="msg_missing_notification_access" msgid="281113995110910548">"Ծանուցումների կետիկները ցուցադրելու համար միացրեք ծանուցումները <xliff:g id="NAME">%1$s</xliff:g>-ի համար"</string>
     <string name="title_change_settings" msgid="1376365968844349552">"Փոխել կարգավորումները"</string>
     <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Ավելացնել պատկերակը Հիմնական էկրանին"</string>
     <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Նոր հավելվածների համար"</string>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index 379a960..5112223 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Hapus"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Uninstal"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"Info aplikasi"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"Instal"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"memasang pintasan"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Mengizinkan aplikasi menambahkan pintasan tanpa campur tangan pengguna."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"membaca setelan dan pintasan layar Utama"</string>
@@ -82,7 +83,7 @@
     <string name="title_missing_notification_access" msgid="7503287056163941064">"Perlu akses notifikasi"</string>
     <string name="msg_missing_notification_access" msgid="281113995110910548">"Guna menampilkan Titik Notifikasi, aktifkan notifikasi aplikasi untuk <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="title_change_settings" msgid="1376365968844349552">"Ubah setelan"</string>
-    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Tambahkan ikon ke layar Utama"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Tambahkan ikon ke Layar utama"</string>
     <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Untuk aplikasi baru"</string>
     <string name="icon_shape_override_label" msgid="2977264953998281004">"Ubah bentuk ikon"</string>
     <string name="icon_shape_system_default" msgid="1709762974822753030">"Gunakan default sistem"</string>
diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml
index cf178a5..e1b04e2 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Fjarlægja"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Fjarlægja"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"Forritsupplýsingar"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"Setja upp"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"setja upp flýtileiðir"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Leyfir forriti að bæta við flýtileiðum án íhlutunar notanda."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"lesa stillingar og flýtileiðir heimaskjás"</string>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index a52aee4..a22a4ce 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Rimuovi"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Disinstalla"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"Informazioni app"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"Installa"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"aggiunta di scorciatoie"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Consente a un\'app di aggiungere scorciatoie automaticamente."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"lettura di impostazioni e scorciatoie in Home"</string>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 650a90d..d7905b4 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"הסר"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"הסר התקנה"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"פרטי אפליקציה"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"התקנה"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"התקן קיצורי דרך"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"מאפשר לאפליקציה להוסיף קיצורי דרך ללא התערבות המשתמש."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"קרא הגדרות וקיצורי דרך של דף הבית"</string>
@@ -93,7 +94,7 @@
     <string name="icon_shape_override_progress" msgid="3461735694970239908">"משנה את הצורה של הסמלים"</string>
     <string name="package_state_unknown" msgid="7592128424511031410">"לא ידוע"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"הסר"</string>
-    <string name="abandoned_search" msgid="891119232568284442">"חפש"</string>
+    <string name="abandoned_search" msgid="891119232568284442">"חיפוש"</string>
     <string name="abandoned_promises_title" msgid="7096178467971716750">"אפליקציה זו אינה מותקנת"</string>
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"האפליקציה של סמל זה אינה מותקנת. ניתן להסיר אותו, או לחפש את האפליקציה ולהתקין אותה ידנית."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"מוריד את <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="PROGRESS">%2$s</xliff:g> הושלמו"</string>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 4071a6c..4b6c3ef 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"削除"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"アンインストール"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"アプリ情報"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"インストール"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"ショートカットのインストール"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"ユーザー操作なしでショートカットを追加することをアプリに許可します。"</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"ホームの設定とショートカットの読み取り"</string>
@@ -83,7 +84,7 @@
     <string name="msg_missing_notification_access" msgid="281113995110910548">"通知ドットを表示するには、「<xliff:g id="NAME">%1$s</xliff:g>」のアプリ通知を ON にしてください"</string>
     <string name="title_change_settings" msgid="1376365968844349552">"設定を変更"</string>
     <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"ホーム画面にアイコンを追加"</string>
-    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"新しいアプリをダウンロードしたときに"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"新しいアプリをダウンロードしたとき"</string>
     <string name="icon_shape_override_label" msgid="2977264953998281004">"アイコンの形の変更"</string>
     <string name="icon_shape_system_default" msgid="1709762974822753030">"システムのデフォルトを使用"</string>
     <string name="icon_shape_square" msgid="633575066111622774">"スクエア"</string>
diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml
index 7e8b46c..ec60583 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"ამოშლა"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"დეინსტალაცია"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"აპის შესახებ"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"ინსტალაცია"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"მალსახმობების დაყენება"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"აპისთვის მალსახმობების დამოუკიდებლად დამატების უფლების მიცემა."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"მთავარი ეკრანის პარამეტრებისა და მალსახმობების წაკითხვა"</string>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
index 50ed9aa..115b752 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Жою"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Жою"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"Қолданба ақпараты"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"Орнату"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"төте пернелерді орнату"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Қолданбаға пайдаланушының қатысуынсыз төте пернелерді қосу мүмкіндігін береді."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"Негізгі экрандағы параметрлер мен төте пернелерді оқу"</string>
diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml
index 7028f7e..9040811 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"យកចេញ"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"លុបការដំឡើង"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"ព័ត៌មាន​កម្មវិធី"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"ដំឡើង"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"ដំឡើង​ផ្លូវកាត់"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"អនុញ្ញាត​ឲ្យ​កម្មវិធី​បន្ថែម​ផ្លូវកាត់​ ដោយ​មិន​ចាំបាច់​​អំពើ​ពី​អ្នក​ប្រើ។"</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"អាន​ការ​កំណត់​ និង​ផ្លូវកាត់​​អេក្រង់​ដើម"</string>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index 9a31649..4156a21 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -45,8 +45,9 @@
     <string name="all_apps_button_label" msgid="8130441508702294465">"ಅಪ್ಲಿಕೇಶನ್‌ಗಳ ಪಟ್ಟಿ"</string>
     <string name="all_apps_home_button_label" msgid="252062713717058851">"ಮುಖಪುಟ"</string>
     <string name="remove_drop_target_label" msgid="7812859488053230776">"ತೆಗೆದುಹಾಕಿ"</string>
-    <string name="uninstall_drop_target_label" msgid="4722034217958379417">"ಅಸ್ಥಾಪಿಸು"</string>
+    <string name="uninstall_drop_target_label" msgid="4722034217958379417">"ಅನ್‌ಇನ್‌ಸ್ಟಾಲ್"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"ಅಪ್ಲಿಕೇಶನ್ ಮಾಹಿತಿ"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"ಸ್ಥಾಪಿಸಿ"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"ಶಾರ್ಟ್‌ಕಟ್‌ಗಳನ್ನು ಸ್ಥಾಪಿಸಿ"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"ಬಳಕೆದಾರರ ಹಸ್ತಕ್ಷೇಪವಿಲ್ಲದೆ ಶಾರ್ಟ್‌ಕಟ್‌ಗಳನ್ನು ಸೇರಿಸಲು ಅಪ್ಲಿಕೇಶನ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"ಮುಖಪುಟದ ಸೆಟ್ಟಿಂಗ್‌ಗಳು ಮತ್ತು ಶಾರ್ಟ್‌ಕಟ್‌ಗಳನ್ನು ಓದಿ"</string>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index e3511d4..d9d8da6 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"삭제"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"제거"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"앱 정보"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"설치"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"바로가기 설치"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"앱이 사용자의 작업 없이 바로가기를 추가할 수 있도록 합니다."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"홈 설정 및 바로가기 읽기"</string>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index 85ba8be..0808214 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Алып салуу"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Чыгарып салуу"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"Колдонмо тууралуу"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"Орнотуу"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"тез чакырмаларды орнотуу"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Колдонмого колдонуучуга кайрылбастан тез чакырма кошууга уруксат берет."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"Үйдүн тууралоолорун жана тез чакырмаларын окуу"</string>
diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml
index 9a50c4b..c1a1e71 100644
--- a/res/values-lo/strings.xml
+++ b/res/values-lo/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"ເອົາ​ອອກ"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"ຖອນ​ການ​ຕິດ​ຕັ້ງ"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"ຂໍ້ມູນແອັບ"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"ຕິດຕັ້ງ"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"ຕິດຕັ້ງທາງລັດ"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"ອະນຸຍາດໃຫ້ແອັບຯ ເພີ່ມທາງລັດໂດຍບໍ່ຕ້ອງຮັບການຢືນຢັນຈາກຜູ່ໃຊ້."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"ອ່ານການຕັ້ງຄ່າໜ້າຫຼັກ ແລະທາງລັດ"</string>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index d415e4d..ca45583 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Ištrinti"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Pašalinti"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"Programos inform."</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"Įdiegti"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"įdiegti sparčiuosius klavišus"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Programai leidžiama pridėti sparčiuosius klavišus be naudotojo įsikišimo."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"skaityti pagrindinio puslapio nustatymus ir sparčiuosius klavišus"</string>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index 2d8bfd8..7a4903a 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -46,7 +46,8 @@
     <string name="all_apps_home_button_label" msgid="252062713717058851">"Sākums"</string>
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Noņemt"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Atinstalēt"</string>
-    <string name="app_info_drop_target_label" msgid="692894985365717661">"Lietotnes informācija"</string>
+    <string name="app_info_drop_target_label" msgid="692894985365717661">"Par lietotni"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"Instalēt"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"instalēt saīsnes"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Ļauj lietotnei pievienot saīsnes, nejautājot lietotājam."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"lasīt sākuma ekrāna iestatījumus un saīsnes"</string>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index 7d95a23..0aaed62 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Отстрани"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Деинсталирај"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"Инф. за апликација"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"Инсталирај"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"инсталирај кратенки"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Овозможува апликацијата да додава кратенки без интервенција на корисникот."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"чита поставки и кратенки на почетна страница"</string>
@@ -68,8 +69,8 @@
     <string name="folder_closed" msgid="4100806530910930934">"Папката е затворена"</string>
     <string name="folder_renamed" msgid="1794088362165669656">"Папката е преименувана во <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format" msgid="6629239338071103179">"Папка: <xliff:g id="NAME">%1$s</xliff:g>"</string>
-    <string name="widget_button_text" msgid="2880537293434387943">"Додатоци"</string>
-    <string name="wallpaper_button_text" msgid="8404103075899945851">"Позадини"</string>
+    <string name="widget_button_text" msgid="2880537293434387943">"Виџети"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"Тапети"</string>
     <string name="settings_button_text" msgid="8873672322605444408">"Поставки за Home"</string>
     <string name="msg_disabled_by_admin" msgid="6898038085516271325">"Оневозможено од администраторот"</string>
     <string name="accessibility_action_overview" msgid="6257665857640347026">"Краток преглед"</string>
@@ -82,7 +83,7 @@
     <string name="title_missing_notification_access" msgid="7503287056163941064">"Потребен е пристап до известувањата"</string>
     <string name="msg_missing_notification_access" msgid="281113995110910548">"За да се прикажуваат „Точки за известување“, вклучете ги известувањата за апликацијата <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="title_change_settings" msgid="1376365968844349552">"Промени ги поставките"</string>
-    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Додајте икона на почетниот екран"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Додај икона на почетниот екран"</string>
     <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"За нови апликации"</string>
     <string name="icon_shape_override_label" msgid="2977264953998281004">"Променете ја формата на иконата"</string>
     <string name="icon_shape_system_default" msgid="1709762974822753030">"Користи ја стандардната поставка на системот"</string>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index 0cc7aa0..299fc45 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"നീക്കംചെയ്യുക"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"അൺഇൻസ്റ്റാളുചെയ്യുക"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"ആപ്പ് വിവരം"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"ഇൻസ്‌റ്റാൾ ചെയ്യുക"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"കുറുക്കുവഴികൾ ഇൻസ്റ്റാളുചെയ്യുക"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"ഉപയോക്തൃ ഇടപെടൽ ഇല്ലാതെ കുറുക്കുവഴികൾ ചേർക്കാൻ അപ്ലിക്കേഷനെ അനുവദിക്കുന്നു."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"ഹോം ക്രമീകരണങ്ങളും കുറുക്കുവഴികളും റീഡുചെയ്യുക"</string>
diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml
index 22479cc..8921c77 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Арилгах"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Устгах"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"Апп-н мэдээлэл"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"Суулгах"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"товчлол суулгах"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Апп нь хэрэглэгчийн оролцоогүйгээр товчлолыг нэмэж чадна"</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"Нүүрний тохиргоо болон товчлолыг унших"</string>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index 751ad22..ca9a402 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -22,7 +22,7 @@
     <string name="app_name" msgid="649227358658669779">"Launcher3"</string>
     <string name="folder_name" msgid="7371454440695724752"></string>
     <string name="work_folder_name" msgid="3753320833950115786">"कार्य"</string>
-    <string name="activity_not_found" msgid="8071924732094499514">"अॅप स्थापित केलेला नाही."</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"अॅप इंस्टॉल केलेला नाही."</string>
     <string name="activity_not_available" msgid="7456344436509528827">"अॅप उपलब्ध नाही"</string>
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"डाउनलोड केलेला अ‍ॅप सुरक्षित मोड मध्‍ये अक्षम केला"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"विजेट सुरक्षित मोडमध्ये अक्षम झाले"</string>
@@ -45,18 +45,19 @@
     <string name="all_apps_button_label" msgid="8130441508702294465">"अॅप्स सूची"</string>
     <string name="all_apps_home_button_label" msgid="252062713717058851">"होम"</string>
     <string name="remove_drop_target_label" msgid="7812859488053230776">"काढा"</string>
-    <string name="uninstall_drop_target_label" msgid="4722034217958379417">"विस्थापित करा"</string>
+    <string name="uninstall_drop_target_label" msgid="4722034217958379417">"अनइंस्टॉल करा"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"अॅप माहिती"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"इंस्टॉल करा"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"शॉर्टकट स्‍थापित करा"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"वापरकर्ता हस्तक्षेपाशिवाय शॉर्टकट जोडण्यास अॅप ला अनुमती देते."</string>
-    <string name="permlab_read_settings" msgid="1941457408239617576">"होम सेटिग्ज आणि शॉर्टकट वाचा"</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"होम सेटिंग्ज आणि शॉर्टकट वाचा"</string>
     <string name="permdesc_read_settings" msgid="5833423719057558387">"मुख्यपृष्ठातील सेटिंग्ज आणि शॉर्टकट वाचण्यास अॅप ला अनुमती देते."</string>
-    <string name="permlab_write_settings" msgid="3574213698004620587">"होम स्क्रीन सेटिंग्ज आणि शॉर्टकट लिहा"</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"होम सेटिंग्ज आणि शॉर्टकट लिहा"</string>
     <string name="permdesc_write_settings" msgid="5440712911516509985">"मुख्यपृष्ठातील सेटिंग्ज आणि शॉर्टकट बदलण्यास अॅप ला अनुमती देते."</string>
     <string name="msg_no_phone_permission" msgid="9208659281529857371">"<xliff:g id="APP_NAME">%1$s</xliff:g> ला फोन कॉल करण्याची अनुमती नाही"</string>
     <string name="gadget_error_text" msgid="6081085226050792095">"विजेट लोड करण्यात समस्या"</string>
     <string name="gadget_setup_text" msgid="8274003207686040488">"सेटअप"</string>
-    <string name="uninstall_system_app_text" msgid="4172046090762920660">"हा सिस्टम अॅप आहे आणि विस्थापित केला जाऊ शकत नाही."</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"हा सिस्टम अॅप आहे आणि अनइंस्टॉल केला जाऊ शकत नाही."</string>
     <string name="folder_hint_text" msgid="6617836969016293992">"अनामित फोल्डर"</string>
     <string name="disabled_app_label" msgid="6673129024321402780">"<xliff:g id="APP_NAME">%1$s</xliff:g> अक्षम केला आहे"</string>
     <string name="default_scroll_format" msgid="7475544710230993317">"%2$d पैकी %1$d पृष्ठ"</string>
@@ -75,7 +76,7 @@
     <string name="accessibility_action_overview" msgid="6257665857640347026">"अवलोकन"</string>
     <string name="allow_rotation_title" msgid="7728578836261442095">"मुख्यस्क्रीन फिरविण्‍यास अनुमती द्या"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"फोन फिरविला जातो तेव्हा"</string>
-    <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"वर्तमान प्रदर्शन सेटिंग फिरविण्यास परवानगी देत नाही"</string>
+    <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"वर्तमान डिस्प्ले सेटिंग रोटेशनला परवानगी देत नाही"</string>
     <string name="icon_badging_title" msgid="874121399231955394">"सूचना बिंदू"</string>
     <string name="icon_badging_desc_on" msgid="2627952638544674079">"चालू"</string>
     <string name="icon_badging_desc_off" msgid="5503319969924580241">"बंद"</string>
@@ -94,10 +95,10 @@
     <string name="package_state_unknown" msgid="7592128424511031410">"अज्ञात"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"काढा"</string>
     <string name="abandoned_search" msgid="891119232568284442">"शोधा"</string>
-    <string name="abandoned_promises_title" msgid="7096178467971716750">"हा अॅप स्थापित केलेला नाही"</string>
-    <string name="abandoned_promise_explanation" msgid="3990027586878167529">"या चिन्हासाठी अॅप स्थापित केलेला नाही. आपण ते काढू शकता किंवा अॅपचा शोध घेऊ शकता आणि त्यास व्यक्तिचलितपणे स्थापित करू शकता."</string>
+    <string name="abandoned_promises_title" msgid="7096178467971716750">"हा अॅप इंस्टॉल केलेला नाही"</string>
+    <string name="abandoned_promise_explanation" msgid="3990027586878167529">"या चिन्हासाठी अॅप इंस्टॉल केलेला नाही. आपण ते काढू शकता किंवा अॅपचा शोध घेऊ शकता आणि त्यास व्यक्तिचलितपणे इंस्टॉल करू शकता."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> डाउनलोड होत आहे , <xliff:g id="PROGRESS">%2$s</xliff:g> पूर्ण झाले"</string>
-    <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> स्थापित करण्याची प्रतिक्षा करीत आहे"</string>
+    <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> इंस्टॉल करण्याची प्रतिक्षा करीत आहे"</string>
     <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> विजेट"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"होम स्क्रीनवर जोडा"</string>
     <string name="action_move_here" msgid="2170188780612570250">"आयटम येथे हलवा"</string>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index f94b167..6aeac8b 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Alih keluar"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Nyahpasang"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"Maklumat apl"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"Pasang"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"pasang pintasan"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Membenarkan apl menambah pintasan tanpa campur tangan pengguna."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"baca tetapan dan pintasan Laman Utama"</string>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index 74574b9..0b2e0d5 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"ဖယ်ရှားမည်"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"ဖယ်ထုတ်မည်"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"အက်ပ်အချက်အလက်များ"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"ထည့်သွင်းရန်"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"အတိုကောက်မှတ်သားမှုများအား ထည့်သွင်းခြင်း"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"အသုံးပြုသူ လုပ်ဆောင်မှုမရှိပဲ အပ်ပလီကေးရှင်းကို အတိုကောက်မှတ်သားမှုများ ပြုလုပ်ခွင့် ပေးခြင်း"</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"ပင်မမျက်နှာစာ အပြင်အဆင် နှင့် အတိုကောက်မှတ်သားမှုများအား ဖတ်ခြင်း"</string>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index 65247c1..0a6276b 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Fjern"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Avinstaller"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"Info om appen"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"Installer"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"installere snarveier"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Gir apper tillatelse til å legge til snarveier uten innblanding fra brukeren."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"lese startsideinnstillinger og -snarveier"</string>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index 904dad4..41e43a7 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"हटाउनुहोस्"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"विस्थापित गर्नुहोस्"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"अनुप्रयोग जानकारी"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"स्थापना गर्नुहोस्"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"सर्टकट स्थापना गर्नेहोस्"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"प्रयोगकर्ताको हस्तक्षेप बिना एउटा अनुप्रयोगलाई सर्टकटमा थप्नको लागि अनुमति दिनुहोस्।"</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"गृह सेटिङहरू र सर्टकटहरू पढ्नुहोस्"</string>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index 7e8def4..16a79ae 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Verwijderen"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Deïnstalleren"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"App-info"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"Installeren"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"Snelle links instellen"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Een app toestaan snelkoppelingen toe te voegen zonder tussenkomst van de gebruiker."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"instellingen en snelkoppelingen op de homepage lezen"</string>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index cf5351c..525f2f6 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -27,10 +27,10 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"ਡਾਊਨਲੋਡ ਕੀਤਾ ਐਪ ਸੁਰੱਖਿਅਤ ਮੋਡ ਵਿੱਚ ਅਸਮਰਥਿਤ"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"ਵਿਜੇਟ ਸੁਰੱਖਿਅਤ ਮੋਡ ਵਿੱਚ ਅਸਮਰਥਿਤ"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"ਸ਼ਾਰਟਕੱਟ ਉਪਲਬਧ ਨਹੀਂ ਹੈ"</string>
-    <string name="home_screen" msgid="806512411299847073">"ਮੁੱਖ ਸਕ੍ਰੀਨ"</string>
-    <string name="custom_actions" msgid="3747508247759093328">"ਵਿਸ਼ੇਸ਼-ਵਿਉਂਤਬੱਧ ਕਾਰਵਾਈਆਂ"</string>
+    <string name="home_screen" msgid="806512411299847073">"ਹੋਮ ਸਕ੍ਰੀਨ"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"ਵਿਉਂਂਤੀ ਕਾਰਵਾਈਆਂ"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"ਇੱਕ ਵਿਜੇਟ ਚੁਣਨ ਲਈ ਛੋਹਵੋT &amp; ਹੋਲਡ ਕਰੋ।"</string>
-    <string name="long_accessible_way_to_add" msgid="4289502106628154155">"ਡਬਲ-ਟੈਪ &amp; ਇੱਕ ਵਿਜੇਟ ਚੁਣਨ ਲਈ ਹੋਲਡ ਕਰੋ ਅਤੇ ਕਸਟਮ ਕਿਰਿਆਵਾਂ ਵਰਤੋ।"</string>
+    <string name="long_accessible_way_to_add" msgid="4289502106628154155">"ਇੱਕ ਵਿਜੇਟ ਚੁਣਨ ਲਈ ਜਾਂ ਵਿਉਂਂਤੀ ਕਾਰਵਾਈਆਂ ਵਰਤਣ ਲਈ ਦੋ ਵਾਰ ਟੈਪ ਕਰੋ ਅਤੇ ਦਬਾ ਕੇ ਰੱਖੋ।"</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d ਚੌੜਾਈ ਅਤੇ %2$d ਲੰਬਾਈ"</string>
     <string name="add_item_request_drag_hint" msgid="5899764264480397019">"ਹੱਥੀਂ ਰੱਖਣ ਲਈ ਸਪੱਰਸ਼ ਕਰੋ ਅਤੇ ਦਬਾਈ ਰੱਖੋ"</string>
@@ -45,15 +45,16 @@
     <string name="all_apps_button_label" msgid="8130441508702294465">"ਐਪ ਸੂਚੀ"</string>
     <string name="all_apps_home_button_label" msgid="252062713717058851">"ਹੋਮ"</string>
     <string name="remove_drop_target_label" msgid="7812859488053230776">"ਹਟਾਓ"</string>
-    <string name="uninstall_drop_target_label" msgid="4722034217958379417">"ਸਥਾਪਨਾ ਰੱਦ ਕਰੋ"</string>
+    <string name="uninstall_drop_target_label" msgid="4722034217958379417">"ਅਣਸਥਾਪਤ ਕਰੋ"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"ਐਪ ਜਾਣਕਾਰੀ"</string>
-    <string name="permlab_install_shortcut" msgid="5632423390354674437">"ਸ਼ਾਰਟਕੱਟ ਇੰਸਟੌਲ ਕਰੋ"</string>
-    <string name="permdesc_install_shortcut" msgid="923466509822011139">"ਇੱਕ ਐਪ ਨੂੰ ਉਪਭੋਗਤਾ ਦੇ ਦਖ਼ਲ ਤੋਂ ਬਿਨਾਂ ਸ਼ਾਰਟਕੱਟ ਜੋੜਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ।"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"ਸਥਾਪਤ ਕਰੋ"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"ਸ਼ਾਰਟਕੱਟ ਸਥਾਪਤ ਕਰੋ"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"ਇੱਕ ਐਪ ਨੂੰ ਵਰਤੋਂਕਾਰ ਦੇ ਦਖ਼ਲ ਤੋਂ ਬਿਨਾਂ ਸ਼ਾਰਟਕੱਟ ਸ਼ਾਮਲ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ।"</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"ਹੋਮ ਸੈਟਿੰਗਾਂ ਅਤੇ ਸ਼ਾਰਟਕੱਟ ਪੜ੍ਹੋ"</string>
     <string name="permdesc_read_settings" msgid="5833423719057558387">"ਐਪ ਨੂੰ ਹੋਮ ਵਿੱਚ ਸੈਟਿੰਗਾਂ ਅਤੇ ਸ਼ਾਰਟਕੱਟ ਪੜ੍ਹਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ।"</string>
     <string name="permlab_write_settings" msgid="3574213698004620587">"ਹੋਮ ਸੈਟਿੰਗਾਂ ਅਤੇ ਸ਼ਾਰਟਕੱਟ ਲਿਖੋ"</string>
     <string name="permdesc_write_settings" msgid="5440712911516509985">"ਐਪ ਨੂੰ ਹੋਮ ਵਿੱਚ ਸੈਟਿੰਗਾਂ ਅਤੇ ਸ਼ਾਰਟਕੱਟ ਬਦਲਣ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ।"</string>
-    <string name="msg_no_phone_permission" msgid="9208659281529857371">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਨੂੰ ਫੋਨ ਕਾਲਾਂ ਕਰਨ ਦੀ ਆਗਿਆ ਨਹੀਂ ਹੈ"</string>
+    <string name="msg_no_phone_permission" msgid="9208659281529857371">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਨੂੰ ਫ਼ੋਨ ਕਾਲਾਂ ਕਰਨ ਦੀ ਆਗਿਆ ਨਹੀਂ ਹੈ"</string>
     <string name="gadget_error_text" msgid="6081085226050792095">"ਵਿਜੇਟ ਲੋਡ ਕਰਨ ਵਿੱਚ ਸਮੱਸਿਆ"</string>
     <string name="gadget_setup_text" msgid="8274003207686040488">"ਸਥਾਪਤ ਕਰੋ"</string>
     <string name="uninstall_system_app_text" msgid="4172046090762920660">"ਇਹ ਇੱਕ ਸਿਸਟਮ ਐਪ ਹੈ ਅਤੇ ਇਸਨੂੰ ਅਣਇੰਸਟੌਲ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ।"</string>
@@ -73,47 +74,47 @@
     <string name="settings_button_text" msgid="8873672322605444408">"ਹੋਮ ਸੈਟਿੰਗਾਂ"</string>
     <string name="msg_disabled_by_admin" msgid="6898038085516271325">"ਤੁਹਾਡੇ ਪ੍ਰਸ਼ਾਸਕ ਦੁਆਰਾ ਅਯੋਗ ਬਣਾਈ ਗਈ"</string>
     <string name="accessibility_action_overview" msgid="6257665857640347026">"ਰੂਪ-ਰੇਖਾ"</string>
-    <string name="allow_rotation_title" msgid="7728578836261442095">"ਮੁੱਖ ਸਕ੍ਰੀਨ ਨੂੰ ਘੁੰਮਾਉਣ ਦੀ ਆਗਿਆ ਦਿਓ"</string>
+    <string name="allow_rotation_title" msgid="7728578836261442095">"ਹੋਮ ਸਕ੍ਰੀਨ ਨੂੰ ਘੁੰਮਾਉਣ ਦੀ ਆਗਿਆ ਦਿਓ"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"ਜਦੋਂ ਫ਼ੋਨ ਘੁੰਮਾਇਆ ਜਾਂਦਾ ਹੈ"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"ਵਰਤਮਾਨ ਡਿਸਪਲੇ ਸੈਟਿੰਗ ਘੁੰਮਾਉਣ ਦੀ ਇਜਾਜ਼ਤ ਨਹੀਂ ਦਿੰਦੀ ਹੈ"</string>
     <string name="icon_badging_title" msgid="874121399231955394">"ਸੂਚਨਾ ਬਿੰਦੂ"</string>
     <string name="icon_badging_desc_on" msgid="2627952638544674079">"ਚਾਲੂ"</string>
     <string name="icon_badging_desc_off" msgid="5503319969924580241">"ਬੰਦ"</string>
     <string name="title_missing_notification_access" msgid="7503287056163941064">"ਸੂਚਨਾ ਪਹੁੰਚ ਲੋੜੀਂਦੀ ਹੈ"</string>
-    <string name="msg_missing_notification_access" msgid="281113995110910548">"ਸੂਚਨਾ ਬਿੰਦੀਆਂ ਦਿਖਾਉਣ ਲਈ, <xliff:g id="NAME">%1$s</xliff:g> ਲਈ ਐਪ ਸੂਚਨਾਵਾਂ ਚਾਲੂ ਕਰੋ"</string>
+    <string name="msg_missing_notification_access" msgid="281113995110910548">"ਸੂਚਨਾ ਬਿੰਦੂਆਂ ਦਿਖਾਉਣ ਲਈ, <xliff:g id="NAME">%1$s</xliff:g> ਲਈ ਐਪ ਸੂਚਨਾਵਾਂ ਚਾਲੂ ਕਰੋ"</string>
     <string name="title_change_settings" msgid="1376365968844349552">"ਸੈਟਿੰਗਾਂ ਬਦਲੋ"</string>
-    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"ਮੁੱਖ ਸਕ੍ਰੀਨ \'ਤੇ ਪ੍ਰਤੀਕ ਸ਼ਾਮਲ ਕਰੋ"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"ਹੋਮ ਸਕ੍ਰੀਨ \'ਤੇ ਪ੍ਰਤੀਕ ਸ਼ਾਮਲ ਕਰੋ"</string>
     <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"ਨਵੀਆਂ ਐਪਾਂ ਲਈ"</string>
-    <string name="icon_shape_override_label" msgid="2977264953998281004">"ਆਈਕਨ ਦੀ ਆਕ੍ਰਿਤੀ ਬਦਲੋ"</string>
+    <string name="icon_shape_override_label" msgid="2977264953998281004">"ਪ੍ਰਤੀਕ ਦੀ ਆਕ੍ਰਿਤੀ ਬਦਲੋ"</string>
     <string name="icon_shape_system_default" msgid="1709762974822753030">"ਸਿਸਟਮ ਦੀ ਪੂਰਵ-ਨਿਰਧਾਰਤ ਸੈਟਿੰਗ ਵਰਤੋ"</string>
     <string name="icon_shape_square" msgid="633575066111622774">"ਵਰਗ"</string>
     <string name="icon_shape_squircle" msgid="5658049910802669495">"ਵਰਗਾਕਾਰ-ਚੱਕਰ"</string>
     <string name="icon_shape_circle" msgid="6550072265930144217">"ਚੱਕਰ"</string>
     <string name="icon_shape_teardrop" msgid="4525869388200835463">"ਹੰਝੂ ਦੀ ਬੂੰਦ"</string>
-    <string name="icon_shape_override_progress" msgid="3461735694970239908">"ਆਈਕਨ ਦੀ ਆਕ੍ਰਿਤੀ ਵਿੱਚ ਤਬਦੀਲੀਆਂ ਨੂੰ ਲਾਗੂ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ"</string>
+    <string name="icon_shape_override_progress" msgid="3461735694970239908">"ਪ੍ਰਤੀਕ ਦੀ ਆਕ੍ਰਿਤੀ ਵਿੱਚ ਤਬਦੀਲੀਆਂ ਨੂੰ ਲਾਗੂ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ"</string>
     <string name="package_state_unknown" msgid="7592128424511031410">"ਅਗਿਆਤ"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"ਹਟਾਓ"</string>
     <string name="abandoned_search" msgid="891119232568284442">"ਖੋਜੋ"</string>
     <string name="abandoned_promises_title" msgid="7096178467971716750">"ਇਹ ਐਪ ਇੰਸਟੌਲ ਨਹੀਂ ਕੀਤਾ ਹੋਇਆ ਹੈ।"</string>
-    <string name="abandoned_promise_explanation" msgid="3990027586878167529">"ਇਸ ਆਈਕਨ ਲਈ ਐਪ ਇੰਸਟੌਲ ਨਹੀਂ ਕੀਤਾ ਹੋਇਆ ਹੈ। ਤੁਸੀਂ ਇਸਨੂੰ ਹਟਾ ਸਕਦੇ ਹੋ ਜਾਂ ਐਪ ਖੋਜ ਸਕਦੇ ਹੋ ਅਤੇ ਇਸਨੂੰ ਮੈਨੂਅਲੀ ਇੰਸਟੌਲ ਕਰ ਸਕਦੇ ਹੋ।"</string>
+    <string name="abandoned_promise_explanation" msgid="3990027586878167529">"ਇਸ ਪ੍ਰਤੀਕ ਲਈ ਐਪ ਸਥਾਪਤ ਨਹੀਂ ਕੀਤਾ ਹੋਇਆ ਹੈ। ਤੁਸੀਂ ਇਸਨੂੰ ਹਟਾ ਸਕਦੇ ਹੋ ਜਾਂ ਐਪ ਖੋਜ ਸਕਦੇ ਹੋ ਅਤੇ ਇਸਨੂੰ ਮੈਨੂਅਲੀ ਸਥਾਪਤ ਕਰ ਸਕਦੇ ਹੋ।"</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> ਡਾਉਨਲੋਡ ਹੋਰ ਰਿਹਾ ਹੈ, <xliff:g id="PROGRESS">%2$s</xliff:g> ਸੰਪੂਰਣ"</string>
-    <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> ਸਥਾਪਿਤ ਕਰਨ ਦੀ ਉਡੀਕ ਕਰ ਰਿਹਾ ਹੈ"</string>
+    <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> ਸਥਾਪਤ ਕਰਨ ਦੀ ਉਡੀਕ ਕਰ ਰਿਹਾ ਹੈ"</string>
     <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> ਵਿਜੇਟ"</string>
-    <string name="action_add_to_workspace" msgid="8902165848117513641">"ਹੋਮ ਸਕ੍ਰੀਨ ਵਿੱਚ ਜੋੜੋ"</string>
+    <string name="action_add_to_workspace" msgid="8902165848117513641">"ਹੋਮ ਸਕ੍ਰੀਨ ਵਿੱਚ ਸ਼ਾਮਲ ਕਰੋ"</string>
     <string name="action_move_here" msgid="2170188780612570250">"ਆਈਟਮ ਨੂੰ ਇੱਥੇ ਮੂਵ ਕਰੋ"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"ਆਈਟਮ ਨੂੰ ਮੁੱਖ ਸਕ੍ਰੀਨ ਵਿੱਚ ਜੋੜਿਆ ਗਿਆ"</string>
+    <string name="item_added_to_workspace" msgid="4211073925752213539">"ਆਈਟਮ ਨੂੰ ਹੋਮ ਸਕ੍ਰੀਨ ਵਿੱਚ ਜੋੜਿਆ ਗਿਆ"</string>
     <string name="item_removed" msgid="851119963877842327">"ਅਈਟਮ ਹਟਾਈ ਗਈ"</string>
     <string name="action_move" msgid="4339390619886385032">"ਆਈਟਮ ਨੂੰ ਮੂਵ ਕਰੋ"</string>
     <string name="move_to_empty_cell" msgid="2833711483015685619">"ਕਤਾਰ <xliff:g id="NUMBER_0">%1$s</xliff:g> ਕਾਲਮ <xliff:g id="NUMBER_1">%2$s</xliff:g> ਵਿੱਚ ਮੂਵ ਕਰੋ"</string>
     <string name="move_to_position" msgid="6750008980455459790">"ਸਥਿਤੀ <xliff:g id="NUMBER">%1$s</xliff:g> ਵਿੱਚ ਮੂਵ ਕਰੋ"</string>
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"ਮਨਪਸੰਦ ਸਥਿਤੀ <xliff:g id="NUMBER">%1$s</xliff:g> ਵਿੱਚ ਮੂਵ ਕਰੋ"</string>
     <string name="item_moved" msgid="4606538322571412879">"ਆਈਟਮ ਮੂਵ ਕੀਤੀ ਗਈ"</string>
-    <string name="add_to_folder" msgid="9040534766770853243">"ਇਸ ਫੋਲਡਰ ਵਿੱਚ ਜੋੜੋ: <xliff:g id="NAME">%1$s</xliff:g>"</string>
-    <string name="add_to_folder_with_app" msgid="4534929978967147231">"<xliff:g id="NAME">%1$s</xliff:g> ਦੇ ਨਾਲ ਫੋਲਡਰ ਵਿੱਚ ਜੋੜੋ"</string>
+    <string name="add_to_folder" msgid="9040534766770853243">"ਇਸ ਫੋਲਡਰ ਵਿੱਚ ਸ਼ਾਮਲ ਕਰੋ: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="add_to_folder_with_app" msgid="4534929978967147231">"<xliff:g id="NAME">%1$s</xliff:g> ਦੇ ਨਾਲ ਫੋਲਡਰ ਵਿੱਚ ਸ਼ਾਮਲ ਕਰੋ"</string>
     <string name="added_to_folder" msgid="4793259502305558003">"ਆਈਟਮ ਨੂੰ ਫੋਲਡਰ ਵਿੱਚ ਜੋੜਿਆ ਗਿਆ"</string>
     <string name="create_folder_with" msgid="4050141361160214248">"ਇਸਦੇ ਨਾਲ ਫੋਲਡਰ ਬਣਾਓ: <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_created" msgid="6409794597405184510">"ਫੋਲਡਰ ਬਣਾਇਆ ਗਿਆ"</string>
-    <string name="action_move_to_workspace" msgid="1603837886334246317">"ਮੁੱਖ ਸਕ੍ਰੀਨ ਵਿੱਚ ਮੂਵ ਕਰੋ"</string>
+    <string name="action_move_to_workspace" msgid="1603837886334246317">"ਹੋਮ ਸਕ੍ਰੀਨ ਵਿੱਚ ਮੂਵ ਕਰੋ"</string>
     <string name="action_move_screen_left" msgid="8854216831569401665">"ਸਕ੍ਰੀਨ ਨੂੰ ਖੱਬੇ ਮੂਵ ਕਰੋ"</string>
     <string name="action_move_screen_right" msgid="329334910274311123">"ਸਕ੍ਰੀਨ ਨੂੰ ਸੱਜੇ ਮੂਵ ਕਰੋ"</string>
     <string name="screen_moved" msgid="266230079505650577">"ਸਕ੍ਰੀਨ ਨੂੰ ਮੂਵ ਕੀਤਾ ਗਿਆ"</string>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index c07f7de..2a01d04 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Usuń"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Odinstaluj"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"O aplikacji"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"Zainstaluj"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"instalowanie skrótów"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Pozwala aplikacji dodawać skróty bez interwencji użytkownika."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"odczytywanie ustawień i skrótów na ekranie głównym"</string>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 100f07b..ee4bf08 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Remover"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Desinstalar"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"Inf. da aplicação"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"Instalar"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"instalar atalhos"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Permite a uma aplicação adicionar atalhos sem a intervenção do utilizador."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"ler definições e atalhos do Ecrã Principal"</string>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index d75159f..b550f6f 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Remover"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Desinstalar"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"Informações do app"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"Instalar"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"instalar atalhos"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Permite que um app adicione atalhos sem intervenção do usuário."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"ler configurações e atalhos da tela inicial"</string>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index 512d96d..9cf1cc6 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Eliminați"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Dezinstalați"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"Informații aplicație"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"Instalați"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"instalează comenzi rapide"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Permite unei aplicații să adauge comenzi rapide fără intervenția utilizatorului."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"citește setări și comenzi rapide pentru ecranul de pornire"</string>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 923f357..bfd014e 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Убрать"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Удалить"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"О приложении"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"Установить"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"Создание ярлыков"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Приложение сможет самостоятельно добавлять ярлыки."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"Доступ к настройкам и ярлыкам главного экрана"</string>
diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml
index f6c42b2..349b7ca 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"ඉවත් කරන්න"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"අස්ථාපනය කරන්න"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"යෙදුම් තොරතුරු"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"ස්ථාපනය කරන්න"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"කෙටිමං ස්ථාපනය කරන්න"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"පරිශීලක මැදිහත්වීමෙන් තොරව කෙටිමං එක් කිරීමට යෙදුමකට අවසර දෙයි."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"මුල් පිටු සැකසීම් සහ කෙටිමං කියවන්න"</string>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index 4dbc407..ba84811 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Odstrániť"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Odinštalovať"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"Info o aplikácii"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"Inštalovať"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"inštalovať odkazy"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Povoľuje aplikácii pridať odkazy bez zásahu používateľa."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"čítanie nastavení a odkazov plochy"</string>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index 0b7d36e..c317c0e 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Odstrani"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Odstrani"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"Podatki o aplikaciji"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"Namesti"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"namestitev bližnjic"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Aplikaciji dovoli dodajanje bližnjic brez posredovanja uporabnika."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"branje nastavitev in bližnjic na začetnem zaslonu"</string>
diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml
index 3e6afee..ed07912 100644
--- a/res/values-sq/strings.xml
+++ b/res/values-sq/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Hiqe"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Çinstalo"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"Informacion mbi aplikacionin"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"Instalo"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"instalo shkurtore"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Lejon një aplikacion të shtojë shkurtore pa ndërhyrjen e përdoruesit."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"lexo cilësimet dhe shkurtoret e ekranit bazë"</string>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index f41e02d..f40366f 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Уклони"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Деинсталирај"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"Информ. о апликацији"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"Инсталирај"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"инсталирање пречица"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Дозвољава апликацији да додаје пречице без интервенције корисника."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"читање подешавања и пречица на почетном екрану"</string>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 6c598ff..ff6b4e8 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Ta bort"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Avinstallera"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"Info om appen"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"Installera"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"installera genvägar"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Tillåter att en app lägger till genvägar utan åtgärd från användaren."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"läsa inställningar och genvägar för startsidan"</string>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index 3de28e0..402f3a6 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -30,7 +30,7 @@
     <string name="home_screen" msgid="806512411299847073">"Skrini ya kwanza"</string>
     <string name="custom_actions" msgid="3747508247759093328">"Vitendo maalum"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Gusa na ushikilie ili kuteua wijeti."</string>
-    <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Gonga mara mbili na ushikilie ile uchague wijeti au utumie vitendo maalum."</string>
+    <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Gusa mara mbili na ushikilie ile uchague wijeti au utumie vitendo maalum."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"Upana wa %1$d na kimo cha %2$d"</string>
     <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Gusa na ushikilie ili uweke mwenyewe"</string>
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Ondoa"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Ondoa"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"Maelezo ya programu"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"Sakinisha"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"kuweka njia za mkato"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Huruhusu programu kuongeza njia za mkato bila mtumiaji kuingilia kati."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"soma mipangilio ya Mwanzo na njia za mkato"</string>
@@ -97,7 +98,7 @@
     <string name="abandoned_clean_this" msgid="7610119707847920412">"Ondoa"</string>
     <string name="abandoned_search" msgid="891119232568284442">"Tafuta"</string>
     <string name="abandoned_promises_title" msgid="7096178467971716750">"Programu hii haijasakinishwa"</string>
-    <string name="abandoned_promise_explanation" msgid="3990027586878167529">"Programu ya ikoni hii haijasakinishwa. Unaweza kuiondoa, au utafute programu na uisakinishe wewe mwenyewe."</string>
+    <string name="abandoned_promise_explanation" msgid="3990027586878167529">"Programu ya aikoni hii haijasakinishwa. Unaweza kuiondoa, au utafute programu na uisakinishe wewe mwenyewe."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> inapakuliwa, <xliff:g id="PROGRESS">%2$s</xliff:g> imekamilika"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> inasubiri kusakinisha"</string>
     <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"Wijeti za <xliff:g id="NAME">%1$s</xliff:g>"</string>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index 1d92581..5d72030 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -46,7 +46,8 @@
     <string name="all_apps_home_button_label" msgid="252062713717058851">"முகப்பு"</string>
     <string name="remove_drop_target_label" msgid="7812859488053230776">"அகற்று"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"நிறுவல் நீக்கு"</string>
-    <string name="app_info_drop_target_label" msgid="692894985365717661">"பயன்பாட்டுத் தகவல்"</string>
+    <string name="app_info_drop_target_label" msgid="692894985365717661">"ஆப்ஸ் தகவல்"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"நிறுவு"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"குறுக்குவழிகளை நிறுவுதல்"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"பயனரின் அனுமதி இல்லாமல் குறுக்குவழிகளைச் சேர்க்கப் பயன்பாட்டை அனுமதிக்கிறது."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"முகப்பின் அமைப்பு மற்றும் குறுக்குவழிகளைப் படித்தல்"</string>
@@ -77,7 +78,7 @@
     <string name="allow_rotation_desc" msgid="8662546029078692509">"மொபைலைச் சுழற்றும் போது"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"தற்போதைய திரை அமைப்பு சுழற்றுவதை அனுமதிக்கவில்லை"</string>
     <string name="icon_badging_title" msgid="874121399231955394">"அறிவிப்புப் புள்ளிகள்"</string>
-    <string name="icon_badging_desc_on" msgid="2627952638544674079">"இயக்கப்பட்டுள்ளது"</string>
+    <string name="icon_badging_desc_on" msgid="2627952638544674079">"ஆன்"</string>
     <string name="icon_badging_desc_off" msgid="5503319969924580241">"முடக்கப்பட்டுள்ளது"</string>
     <string name="title_missing_notification_access" msgid="7503287056163941064">"அறிவிப்பிற்கான அணுகல் தேவை"</string>
     <string name="msg_missing_notification_access" msgid="281113995110910548">"அறிவிப்புப் புள்ளிகளைக் காட்ட, <xliff:g id="NAME">%1$s</xliff:g> இன் பயன்பாட்டு அறிவிப்புகளை இயக்கவும்"</string>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index 6c66699..4db5352 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -26,7 +26,7 @@
     <string name="activity_not_available" msgid="7456344436509528827">"యాప్ అందుబాటులో లేదు"</string>
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"డౌన్‌లోడ్ చేసిన యాప్ సురక్షిత మోడ్‌లో నిలిపివేయబడింది"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"సురక్షిత మోడ్‌లో విడ్జెట్‌లు నిలిపివేయబడ్డాయి"</string>
-    <string name="shortcut_not_available" msgid="2536503539825726397">"సత్వరమార్గం అందుబాటులో లేదు"</string>
+    <string name="shortcut_not_available" msgid="2536503539825726397">"షార్ట్‌కట్ అందుబాటులో లేదు"</string>
     <string name="home_screen" msgid="806512411299847073">"హోమ్ స్క్రీన్"</string>
     <string name="custom_actions" msgid="3747508247759093328">"అనుకూల చర్యలు"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"విడ్జెట్‌ను ఎంచుకోవడానికి తాకి &amp; నొక్కి పెట్టండి."</string>
@@ -38,7 +38,7 @@
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"అప్లికేషన్‌లను శోధించండి"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"అప్లికేషన్‌లను లోడ్ చేస్తోంది…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\"కి సరిపోలే అప్లికేషన్‌లేవీ కనుగొనబడలేదు"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"మరిన్ని అనువర్తనాల కోసం శోధించు"</string>
+    <string name="all_apps_search_market_message" msgid="1366263386197059176">"మరిన్ని యాప్‌ల కోసం వెతుకు"</string>
     <string name="notifications_header" msgid="1404149926117359025">"నోటిఫికేషన్‌లు"</string>
     <string name="out_of_space" msgid="4691004494942118364">"ఈ హోమ్ స్క్రీన్‌లో ఖాళీ లేదు."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"ఇష్టమైనవి ట్రేలో ఖాళీ లేదు"</string>
@@ -46,7 +46,8 @@
     <string name="all_apps_home_button_label" msgid="252062713717058851">"హోమ్"</string>
     <string name="remove_drop_target_label" msgid="7812859488053230776">"తీసివేయి"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"అన్ఇన్‌స్టాల్ చేయి"</string>
-    <string name="app_info_drop_target_label" msgid="692894985365717661">"అనువర్తన సమాచారం"</string>
+    <string name="app_info_drop_target_label" msgid="692894985365717661">"యాప్ సమాచారం"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"ఇన్‌స్టాల్ చేయండి"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"సత్వరమార్గాలను ఇన్‌స్టాల్ చేయడం"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"వినియోగదారు ప్రమేయం లేకుండా సత్వరమార్గాలను జోడించడానికి అనువర్తనాన్ని అనుమతిస్తుంది."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"హోమ్ సెట్టింగ్‌లు మరియు సత్వరమార్గాలను చదవడం"</string>
@@ -83,7 +84,7 @@
     <string name="msg_missing_notification_access" msgid="281113995110910548">"నోటిఫికేషన్ డాట్‌లను చూపించడానికి <xliff:g id="NAME">%1$s</xliff:g>కు యాప్ నోటిఫికేషన్‌లను ఆన్ చేయండి"</string>
     <string name="title_change_settings" msgid="1376365968844349552">"సెట్టింగ్‌లను మార్చు"</string>
     <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"హోమ్ స్క్రీన్‌కి చిహ్నాన్ని జోడించు"</string>
-    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"కొత్త అనువర్తనాల కోసం"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"కొత్త యాప్‌ల కోసం"</string>
     <string name="icon_shape_override_label" msgid="2977264953998281004">"చిహ్న ఆకారాన్ని మార్చు"</string>
     <string name="icon_shape_system_default" msgid="1709762974822753030">"సిస్టమ్ డిఫాల్ట్‌ను ఉపయోగించండి"</string>
     <string name="icon_shape_square" msgid="633575066111622774">"చతురస్రం"</string>
@@ -93,7 +94,7 @@
     <string name="icon_shape_override_progress" msgid="3461735694970239908">"చిహ్న ఆకార మార్పులను వర్తింపజేస్తోంది"</string>
     <string name="package_state_unknown" msgid="7592128424511031410">"తెలియదు"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"తీసివేయి"</string>
-    <string name="abandoned_search" msgid="891119232568284442">"శోధించు"</string>
+    <string name="abandoned_search" msgid="891119232568284442">"వెతుకు"</string>
     <string name="abandoned_promises_title" msgid="7096178467971716750">"ఈ యాప్ ఇన్‌స్టాల్ చేయబడలేదు"</string>
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"ఈ చిహ్నం యొక్క యాప్ ఇన్‌స్టాల్ చేయబడలేదు. మీరు దీన్ని తీసివేయవచ్చు లేదా ఆ యాప్ కోసం శోధించి దాన్ని మాన్యువల్‌గా ఇన్‌స్టాల్ చేయవచ్చు."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> డౌన్‌లోడ్ అవుతోంది, <xliff:g id="PROGRESS">%2$s</xliff:g> పూర్తయింది"</string>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index b370099..ae5aeb5 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"นำออก"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"ถอนการติดตั้ง"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"ข้อมูลแอป"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"ติดตั้ง"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"ติดตั้งทางลัด"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"อนุญาตให้แอปเพิ่มทางลัดโดยไม่ต้องให้ผู้ใช้จัดการ"</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"อ่านการตั้งค่าและทางลัดหน้าแรกแล้ว"</string>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index 54135c9..11f13b0 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Alisin"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"I-uninstall"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"Impormasyon ng app"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"I-install"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"i-install ang mga shortcut"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Pinapayagan ang isang app na magdagdag ng mga shortcut nang walang panghihimasok ng user."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"basahin ang mga setting at shortcut ng Home"</string>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index cb8b50a..f19cd78 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Kaldır"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Yüklemeyi kaldır"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"Uygulama bilgileri"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"Yükle"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"kısayolları yükle"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Uygulamaya, kullanıcı müdahalesi olmadan kısayol ekleme izni verir."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"Ana ekran ayarlarını ve kısayollarını oku"</string>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index a9e0109..08f1575 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Видалити"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Видалити"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"Про додаток"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"Установити"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"створення ярликів"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Дозволяє програмі самостійно додавати ярлики."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"читати налаштування та ярлики головного екрана"</string>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
index a704fab..99b8c80 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"ہٹائیں"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"اَن انسٹال کریں"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"ایپ کی معلومات"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"انسٹال کریں"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"شارٹ کٹس انسٹال کریں"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"کسی ایپ کو صارف کی مداخلت کے بغیر شارٹ کٹس شامل کرنے کی اجازت دیتا ہے۔"</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"ہوم ترتیبات اور شارٹ کٹس کو پڑھیں"</string>
diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml
index 7e31889..5167670 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Olib tashlash"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"O‘chirib tashlash"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"Ilova haqida"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"O‘rnatish"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"yorliqlar yaratish"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Ilovalarga foydalanuvchidan so‘ramasdan yorliqlar qo‘shishga ruxsat beradi."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"Uy sozlamalari va yorliqlarini o‘qish"</string>
@@ -76,11 +77,11 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"Asosiy ekranni aylantirishga ruxsat berish"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Telefon burilganda"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Ekran sozlamalariga ko‘ra uni aylantirib bo‘lmaydi"</string>
-    <string name="icon_badging_title" msgid="874121399231955394">"Bildirishnoma nuqtalari"</string>
+    <string name="icon_badging_title" msgid="874121399231955394">"Bildirishnoma belgilari"</string>
     <string name="icon_badging_desc_on" msgid="2627952638544674079">"Yoniq"</string>
     <string name="icon_badging_desc_off" msgid="5503319969924580241">"O‘chiq"</string>
     <string name="title_missing_notification_access" msgid="7503287056163941064">"Bildirishnomalarga ruxsat berilmagan"</string>
-    <string name="msg_missing_notification_access" msgid="281113995110910548">"Bildirishnoma nuqtalarini ko‘rsatish uchun <xliff:g id="NAME">%1$s</xliff:g> ilovasida bildirishnomalarni yoqing"</string>
+    <string name="msg_missing_notification_access" msgid="281113995110910548">"Bildirishnoma belgilarini ko‘rsatish uchun <xliff:g id="NAME">%1$s</xliff:g> ilovasida bildirishnomalarni yoqing"</string>
     <string name="title_change_settings" msgid="1376365968844349552">"Sozlamalarni o‘zgartirish"</string>
     <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Bosh ekranga ikonka qo‘shish"</string>
     <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Yangi o‘rnatilgan ilovalar ikonkasini bosh ekranga chiqarish"</string>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index 83e1cea..c0e1454 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Xóa"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Gỡ cài đặt"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"Thông tin ứng dụng"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"Cài đặt"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"cài đặt lối tắt"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Cho phép ứng dụng thêm lối tắt mà không cần sự can thiệp của người dùng."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"đọc cài đặt và lối tắt trên Màn hình chính"</string>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index bef12fb..2342133 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"移除"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"卸载"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"应用信息"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"安装"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"安装快捷方式"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"允许应用自行添加快捷方式。"</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"读取主屏幕设置和快捷方式"</string>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index 7ac5553..f87ff7f 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"移除"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"解除安裝"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"應用程式資料"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"安裝"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"安裝捷徑"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"允許應用程式無需使用者許可也可新增捷徑。"</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"讀取主畫面的設定和捷徑"</string>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index e0c4c99..814a6e4 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"移除"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"解除安裝"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"應用程式資訊"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"安裝"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"安裝捷徑"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"允許應用程式自動新增捷徑。"</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"讀取主螢幕的設定和捷徑"</string>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index ef6fdeb..35fa1c7 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -47,6 +47,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Susa"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Khipha"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"Ulwazi lohlelo lokusebenza"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"Faka"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"faka izinqamuleli"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Ivumela uhlelo lokusebenza ukufaka izinqamuleli ngaphandle kokungenelela komsebenzisi."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"funda izilungiselelo zokuthi Ikhaya nezinqamuleli"</string>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 5aee715..ad5f0b8 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -63,13 +63,6 @@
         <attr name="pageIndicator" format="reference" />
     </declare-styleable>
 
-    <!-- BaseContainerView specific attributes. These attributes are used to customize
-         AllApps view and WidgetsView in xml. -->
-    <declare-styleable name="BaseContainerView">
-        <!-- Drawable to use for the reveal animation -->
-        <attr name="revealBackground" format="reference" />
-    </declare-styleable>
-
     <!-- XML attributes used by default_workspace.xml -->
     <declare-styleable name="Favorite">
         <attr name="className" format="string" />
@@ -144,4 +137,19 @@
     <declare-styleable name="RecyclerViewFastScroller">
         <attr name="canThumbDetach" format="boolean" />
     </declare-styleable>
+
+    <declare-styleable name="CustomAppWidgetProviderInfo">
+        <attr name="providerId" format="integer" />
+
+        <attr name="android:label" />
+        <attr name="android:initialLayout" />
+        <attr name="android:icon" />
+        <attr name="android:previewImage" />
+        <attr name="android:resizeMode" />
+
+        <attr name="numRows" />
+        <attr name="numColumns" />
+        <attr name="numMinRows" format="integer" />
+        <attr name="numMinColumns" format="integer" />
+    </declare-styleable>
 </resources>
diff --git a/res/values/config.xml b/res/values/config.xml
index 10b612b..0ff0d75 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -1,8 +1,4 @@
 <resources>
-<!-- Dynamic Grid -->
-    <!-- Out of 100, the percent of space the overview bar should try and take vertically. -->
-    <integer name="config_dynamic_grid_overview_icon_zone_percentage">22</integer>
-
 <!-- Miscellaneous -->
     <bool name="config_largeHeap">false</bool>
     <bool name="is_tablet">false</bool>
@@ -46,21 +42,10 @@
 
 <!-- AllApps & Launcher transitions -->
     <!-- The alpha of the AppsCustomize bg in spring loaded mode -->
-    <integer name="config_workspaceScrimAlpha">30</integer>
-    <integer name="config_allAppsTransitionTime">100</integer>
-    <integer name="config_overviewTransitionTime">250</integer>
+    <integer name="config_workspaceScrimAlpha">76</integer>
 
     <!-- Out of 100, the percent to shrink the workspace during spring loaded mode. -->
     <integer name="config_workspaceSpringLoadShrinkPercentage">90</integer>
-    <!-- Out of 100, the percent to shrink the workspace during overview mode. -->
-    <integer name="config_workspaceOverviewShrinkPercentage">70</integer>
-
-    <!-- Fade/zoom in/out duration & scale in a Launcher overlay transition.
-         Note: This should be less than the config_overlayTransitionTime as they happen together. -->
-    <integer name="config_overlayRevealTime">220</integer>
-    <integer name="config_overlaySlideRevealTime">320</integer>
-    <integer name="config_overlayTransitionTime">300</integer>
-    <integer name="config_overlayItemsAlphaStagger">60</integer>
 
     <!-- This constant stores the ratio of the all apps button drawable which
          is used for internal (baked-in) padding -->
@@ -86,9 +71,7 @@
     <integer name="config_dropAnimMaxDuration">500</integer>
 
     <!-- The duration of the UserFolder opening and closing animation -->
-    <integer name="config_folderExpandDuration">120</integer>
     <integer name="config_materialFolderExpandDuration">200</integer>
-    <integer name="config_materialFolderExpandStagger">60</integer>
     <integer name="config_folderDelay">30</integer>
 
     <!-- The distance at which the animation should take the max duration -->
@@ -128,9 +111,6 @@
     <!-- View ID used by cell layout to jail its content -->
     <item type="id" name="cell_layout_jail_id" />
 
-    <!-- View ID used by PreviewImageView to cache its instance -->
-    <item type="id" name="preview_image_id" />
-
 <!-- Popup items -->
     <integer name="config_popupOpenCloseDuration">150</integer>
     <integer name="config_popupArrowOpenDuration">80</integer>
@@ -154,4 +134,8 @@
     <item type="id" name="search_container_hotseat" />
     <item type="id" name="search_container_all_apps" />
 
+<!-- Recents -->
+    <integer name="config_recentsMaxThumbnailCacheSize">6</integer>
+    <integer name="config_recentsMaxIconCacheSize">12</integer>
+
 </resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index b1f9d63..efe5043 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -15,7 +15,10 @@
 -->
 
 <resources>
-<!-- Dynamic Grid -->
+
+    <dimen name="click_shadow_elevation">4dp</dimen>
+
+    <!-- Dynamic Grid -->
     <dimen name="dynamic_grid_edge_margin">8dp</dimen>
     <dimen name="dynamic_grid_min_page_indicator_size">32dp</dimen>
     <dimen name="dynamic_grid_page_indicator_line_height">1dp</dimen>
@@ -54,7 +57,6 @@
     <dimen name="vert_drop_target_horizontal_gap">14dp</dimen>
 
 <!-- App Widget resize frame -->
-    <dimen name="default_widget_padding">8dp</dimen>
     <dimen name="widget_handle_margin">13dp</dimen>
     <dimen name="resize_frame_background_padding">24dp</dimen>
 
@@ -87,10 +89,9 @@
     <dimen name="all_apps_empty_search_bg_top_offset">144dp</dimen>
     <dimen name="all_apps_background_canvas_width">700dp</dimen>
     <dimen name="all_apps_background_canvas_height">475dp</dimen>
-    <dimen name="all_apps_caret_stroke_width">2dp</dimen>
-    <dimen name="all_apps_caret_shadow_spread">1dp</dimen>
-    <dimen name="all_apps_caret_size">13dp</dimen>
     <dimen name="all_apps_caret_workspace_offset">18dp</dimen>
+    <dimen name="all_apps_header_tab_height">50dp</dimen>
+    <dimen name="all_apps_tabs_indicator_height">2dp</dimen>
 
 <!-- Search bar in All Apps -->
     <dimen name="all_apps_header_max_elevation">3dp</dimen>
@@ -180,6 +181,7 @@
     <dimen name="deep_shortcut_drag_handle_size">16dp</dimen>
     <dimen name="popup_padding_start">10dp</dimen>
     <dimen name="popup_padding_end">16dp</dimen>
+    <dimen name="popup_vertical_padding">4dp</dimen>
     <dimen name="popup_arrow_width">10dp</dimen>
     <dimen name="popup_arrow_height">8dp</dimen>
     <dimen name="popup_arrow_vertical_offset">-2dp</dimen>
@@ -227,14 +229,12 @@
     <dimen name="notification_footer_icon_size">18dp</dimen>
     <!-- notification_icon_size + notification_padding_end + 16dp padding between icon and text -->
     <dimen name="notification_main_text_padding_end">52dp</dimen>
-    <dimen name="notification_elevation">2dp</dimen>
     <dimen name="horizontal_ellipsis_size">18dp</dimen>
     <!-- arrow_horizontal_offset_start - (ellipsis_size - arrow_width) / 2 -->
     <dimen name="horizontal_ellipsis_offset">19dp</dimen>
     <dimen name="popup_item_divider_height">0.5dp</dimen>
     <dimen name="swipe_helper_falsing_threshold">70dp</dimen>
 
-<!-- Other -->
-    <!-- Approximates the system status bar height. Not guaranteed to be always be correct. -->
-    <dimen name="status_bar_height">24dp</dimen>
+<!-- Recents -->
+    <dimen name="recents_page_spacing">10dp</dimen>
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 1197b1c..fdd4d8d 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -90,6 +90,8 @@
     <string name="uninstall_drop_target_label">Uninstall</string>
     <!-- Label for app info drop target. [CHAR_LIMIT=20] -->
     <string name="app_info_drop_target_label">App info</string>
+    <!-- Label for install drop target. [CHAR_LIMIT=20] -->
+    <string name="install_drop_target_label">Install</string>
 
     <!-- Permissions: -->
     <skip />
@@ -316,4 +318,10 @@
     <!-- Accessibility confirmation for notification being dismissed. -->
     <string name="notification_dismissed">Notification dismissed</string>
 
+    <!-- Label of tab to indicate personal apps -->
+    <string name="all_apps_personal_tab">Personal</string>
+
+    <!-- Label of tab to indicate work apps -->
+    <string name="all_apps_work_tab">Work</string>
+
 </resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 8129e81..8cc4743 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -136,13 +136,6 @@
     <style name="PopupItem">
         <item name="android:colorControlHighlight">?attr/popupColorTertiary</item>
     </style>
-    <style name="PopupGutter">
-        <item name="android:backgroundTintMode">multiply</item>
-        <item name="android:backgroundTint">?attr/popupColorSecondary</item>
-        <item name="android:background">@drawable/gutter_horizontal</item>
-        <item name="android:elevation">@dimen/notification_elevation</item>
-        <item name="android:outlineProvider">none</item>
-    </style>
 
     <!-- Drop targets -->
     <style name="DropTargetButtonBase">
diff --git a/res/xml/custom_widgets.xml b/res/xml/custom_widgets.xml
new file mode 100644
index 0000000..4b54386
--- /dev/null
+++ b/res/xml/custom_widgets.xml
@@ -0,0 +1,31 @@
+<?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.
+-->
+
+<widgets>
+    <!-- Sample widget definition
+        <widget
+            android:label="My custom widget"
+            android:initialLayout="@layout/sample_widget_layout"
+            android:icon="@drawable/ic_launcher_home"
+            android:resizeMode="horizontal|vertical"
+            launcher:numRows="2"
+            launcher:numColumns="3"
+            launcher:numMinRows="1"
+            launcher:numMinColumns="2"
+            launcher:providerId="1" />
+    -->
+</widgets>
\ No newline at end of file
diff --git a/res/xml/device_profiles.xml b/res/xml/device_profiles.xml
index c582fc5..30c1c54 100644
--- a/res/xml/device_profiles.xml
+++ b/res/xml/device_profiles.xml
@@ -15,7 +15,7 @@
      limitations under the License.
 -->
 
-<profiles xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3" >
+<profiles xmlns:launcher="http://schemas.android.com/apk/res-auto" >
 
     <profile
         launcher:name="Super Short Stubby"
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index 597e937..26024e5 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -25,6 +25,8 @@
 import android.widget.LinearLayout;
 
 import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
+import com.android.launcher3.util.TouchController;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -32,18 +34,28 @@
 /**
  * Base class for a View which shows a floating UI on top of the launcher UI.
  */
-public abstract class AbstractFloatingView extends LinearLayout {
+public abstract class AbstractFloatingView extends LinearLayout implements TouchController {
 
     @IntDef(flag = true, value = {
             TYPE_FOLDER,
-            TYPE_POPUP_CONTAINER_WITH_ARROW,
-            TYPE_WIDGETS_BOTTOM_SHEET
+            TYPE_ACTION_POPUP,
+            TYPE_WIDGETS_BOTTOM_SHEET,
+            TYPE_WIDGET_RESIZE_FRAME,
+            TYPE_WIDGETS_FULL_SHEET,
+            TYPE_QUICKSTEP_PREVIEW
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface FloatingViewType {}
     public static final int TYPE_FOLDER = 1 << 0;
-    public static final int TYPE_POPUP_CONTAINER_WITH_ARROW = 1 << 1;
+    public static final int TYPE_ACTION_POPUP = 1 << 1;
     public static final int TYPE_WIDGETS_BOTTOM_SHEET = 1 << 2;
+    public static final int TYPE_WIDGET_RESIZE_FRAME = 1 << 3;
+    public static final int TYPE_WIDGETS_FULL_SHEET = 1 << 4;
+    public static final int TYPE_QUICKSTEP_PREVIEW = 1 << 5;
+
+    public static final int TYPE_ALL = TYPE_FOLDER | TYPE_ACTION_POPUP
+            | TYPE_WIDGETS_BOTTOM_SHEET | TYPE_WIDGET_RESIZE_FRAME | TYPE_WIDGETS_FULL_SHEET
+            | TYPE_QUICKSTEP_PREVIEW;
 
     protected boolean mIsOpen;
 
@@ -72,21 +84,7 @@
 
     protected abstract void handleClose(boolean animate);
 
-    /**
-     * If the view is current handling keyboard, return the active target, null otherwise
-     */
-    public ExtendedEditText getActiveTextView() {
-        return null;
-    }
-
-
-    /**
-     * Any additional view (outside of this container) where touch should be allowed while this
-     * view is visible.
-     */
-    public View getExtendedTouchView() {
-        return null;
-    }
+    public abstract void logActionCommand(int command);
 
     public final boolean isOpen() {
         return mIsOpen;
@@ -97,6 +95,16 @@
 
     protected abstract boolean isOfType(@FloatingViewType int type);
 
+    public void onBackPressed() {
+        logActionCommand(Action.Command.BACK);
+        close(true);
+    }
+
+    @Override
+    public boolean onControllerTouchEvent(MotionEvent ev) {
+        return false;
+    }
+
     protected static <T extends AbstractFloatingView> T getOpenView(
             Launcher launcher, @FloatingViewType int type) {
         DragLayer dragLayer = launcher.getDragLayer();
@@ -121,26 +129,31 @@
         }
     }
 
-    public static void closeAllOpenViews(Launcher launcher, boolean animate) {
+    public static void closeOpenViews(Launcher launcher, boolean animate,
+            @FloatingViewType int type) {
         DragLayer dragLayer = launcher.getDragLayer();
         // Iterate in reverse order. AbstractFloatingView is added later to the dragLayer,
         // and will be one of the last views.
         for (int i = dragLayer.getChildCount() - 1; i >= 0; i--) {
             View child = dragLayer.getChildAt(i);
             if (child instanceof AbstractFloatingView) {
-                ((AbstractFloatingView) child).close(animate);
+                AbstractFloatingView abs = (AbstractFloatingView) child;
+                if (abs.isOfType(type)) {
+                    abs.close(animate);
+                }
             }
         }
     }
 
+    public static void closeAllOpenViews(Launcher launcher, boolean animate) {
+        closeOpenViews(launcher, animate, TYPE_ALL);
+    }
+
     public static void closeAllOpenViews(Launcher launcher) {
         closeAllOpenViews(launcher, true);
     }
 
     public static AbstractFloatingView getTopOpenView(Launcher launcher) {
-        return getOpenView(launcher, TYPE_FOLDER | TYPE_POPUP_CONTAINER_WITH_ARROW
-                | TYPE_WIDGETS_BOTTOM_SHEET);
+        return getOpenView(launcher, TYPE_ALL);
     }
-
-    public abstract int getLogContainerType();
 }
diff --git a/src/com/android/launcher3/AllAppsList.java b/src/com/android/launcher3/AllAppsList.java
index 8ac8570..5eb6cc7 100644
--- a/src/com/android/launcher3/AllAppsList.java
+++ b/src/com/android/launcher3/AllAppsList.java
@@ -154,7 +154,7 @@
         for (int i = data.size() - 1; i >= 0; i--) {
             AppInfo info = data.get(i);
             if (matcher.matches(info, info.componentName)) {
-                info.isDisabled = op.apply(info.isDisabled);
+                info.runtimeStatusFlags = op.apply(info.runtimeStatusFlags);
                 modified.add(info);
             }
         }
diff --git a/src/com/android/launcher3/AppInfo.java b/src/com/android/launcher3/AppInfo.java
index 7d2f753..9796d18 100644
--- a/src/com/android/launcher3/AppInfo.java
+++ b/src/com/android/launcher3/AppInfo.java
@@ -21,9 +21,12 @@
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.LauncherActivityInfo;
+import android.os.Build;
+import android.os.Process;
 import android.os.UserHandle;
 
 import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.PackageManagerHelper;
 
@@ -32,10 +35,6 @@
  */
 public class AppInfo extends ItemInfoWithIcon {
 
-    public static final int FLAG_SYSTEM_UNKNOWN = 0;
-    public static final int FLAG_SYSTEM_YES = 1 << 0;
-    public static final int FLAG_SYSTEM_NO = 1 << 1;
-
     /**
      * The intent used to start the application.
      */
@@ -43,16 +42,6 @@
 
     public ComponentName componentName;
 
-    /**
-     * {@see ShortcutInfo#isDisabled}
-     */
-    public int isDisabled = ShortcutInfo.DEFAULT;
-
-    /**
-     * Stores if the app is a system app or not.
-     */
-    public int isSystemApp;
-
     public AppInfo() {
         itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
     }
@@ -73,18 +62,12 @@
         this.componentName = info.getComponentName();
         this.container = ItemInfo.NO_ID;
         this.user = user;
-        if (PackageManagerHelper.isAppSuspended(info.getApplicationInfo())) {
-            isDisabled |= ShortcutInfo.FLAG_DISABLED_SUSPENDED;
-        }
-        if (quietModeEnabled) {
-            isDisabled |= ShortcutInfo.FLAG_DISABLED_QUIET_USER;
-        }
-
         intent = makeLaunchIntent(info);
 
-        isSystemApp = (info.getApplicationInfo().flags & ApplicationInfo.FLAG_SYSTEM) == 0
-                ? FLAG_SYSTEM_NO : FLAG_SYSTEM_YES;
-
+        if (quietModeEnabled) {
+            runtimeStatusFlags |= FLAG_DISABLED_QUIET_USER;
+        }
+        updateRuntimeFlagsForActivityTarget(this, info);
     }
 
     public AppInfo(AppInfo info) {
@@ -92,8 +75,6 @@
         componentName = info.componentName;
         title = Utilities.trim(info.title);
         intent = new Intent(info.intent);
-        isDisabled = info.isDisabled;
-        isSystemApp = info.isSystemApp;
     }
 
     @Override
@@ -120,8 +101,20 @@
                 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
     }
 
-    @Override
-    public boolean isDisabled() {
-        return isDisabled != 0;
+    public static void updateRuntimeFlagsForActivityTarget(
+            ItemInfoWithIcon info, LauncherActivityInfo lai) {
+        ApplicationInfo appInfo = lai.getApplicationInfo();
+        if (PackageManagerHelper.isAppSuspended(appInfo)) {
+            info.runtimeStatusFlags |= FLAG_DISABLED_SUSPENDED;
+        }
+        info.runtimeStatusFlags |= (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0
+                ? FLAG_SYSTEM_NO : FLAG_SYSTEM_YES;
+
+        if (FeatureFlags.LEGACY_ICON_TREATMENT && Utilities.ATLEAST_OREO
+                && appInfo.targetSdkVersion >= Build.VERSION_CODES.O
+                && Process.myUserHandle().equals(lai.getUser())) {
+            // The icon for a non-primary user is badged, hence it's not exactly an adaptive icon.
+            info.runtimeStatusFlags |= FLAG_ADAPTIVE_ICON;
+        }
     }
 }
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index a486a3a..1e95333 100644
--- a/src/com/android/launcher3/AppWidgetResizeFrame.java
+++ b/src/com/android/launcher3/AppWidgetResizeFrame.java
@@ -8,22 +8,19 @@
 import android.appwidget.AppWidgetHostView;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.Context;
-import android.content.res.Resources;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
-import android.widget.FrameLayout;
+import android.view.ViewGroup;
 
 import com.android.launcher3.accessibility.DragViewStateAnnouncer;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.util.FocusLogic;
-import com.android.launcher3.util.TouchController;
 
-public class AppWidgetResizeFrame extends FrameLayout
-        implements View.OnKeyListener, TouchController {
+public class AppWidgetResizeFrame extends AbstractFloatingView implements View.OnKeyListener {
     private static final int SNAP_DURATION = 150;
     private static final float DIMMED_HANDLE_ALPHA = 0f;
     private static final float RESIZE_THRESHOLD = 0.66f;
@@ -109,12 +106,28 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
 
+        ViewGroup content = (ViewGroup) getChildAt(0);
         for (int i = 0; i < HANDLE_COUNT; i ++) {
-            mDragHandles[i] = getChildAt(i);
+            mDragHandles[i] = content.getChildAt(i);
         }
     }
 
-    public void setupForWidget(LauncherAppWidgetHostView widgetView, CellLayout cellLayout,
+    public static void showForWidget(LauncherAppWidgetHostView widget, CellLayout cellLayout) {
+        Launcher launcher = Launcher.getLauncher(cellLayout.getContext());
+        AbstractFloatingView.closeAllOpenViews(launcher);
+
+        DragLayer dl = launcher.getDragLayer();
+        AppWidgetResizeFrame frame = (AppWidgetResizeFrame) launcher.getLayoutInflater()
+                .inflate(R.layout.app_widget_resize_frame, dl, false);
+        frame.setupForWidget(widget, cellLayout, dl);
+        ((DragLayer.LayoutParams) frame.getLayoutParams()).customPosition = true;
+
+        dl.addView(frame);
+        frame.mIsOpen = true;
+        frame.snapToWidget(false);
+    }
+
+    private void setupForWidget(LauncherAppWidgetHostView widgetView, CellLayout cellLayout,
             DragLayer dragLayer) {
         mCellLayout = cellLayout;
         mWidgetView = widgetView;
@@ -126,14 +139,8 @@
         mMinHSpan = info.minSpanX;
         mMinVSpan = info.minSpanY;
 
-        if (!info.isCustomWidget) {
-            mWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(getContext(),
-                    widgetView.getAppWidgetInfo().provider, null);
-        } else {
-            Resources r = getContext().getResources();
-            int padding = r.getDimensionPixelSize(R.dimen.default_widget_padding);
-            mWidgetPadding = new Rect(padding, padding, padding, padding);
-        }
+        mWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(getContext(),
+                widgetView.getAppWidgetInfo().provider, null);
 
         if (mResizeMode == AppWidgetProviderInfo.RESIZE_HORIZONTAL) {
             mDragHandles[INDEX_TOP].setVisibility(GONE);
@@ -391,7 +398,7 @@
         out.bottom = out.top + height;
     }
 
-    public void snapToWidget(boolean animate) {
+    private void snapToWidget(boolean animate) {
         getSnappedRectRelativeToDragLayer(sTmpRect);
         int newWidth = sTmpRect.width();
         int newHeight = sTmpRect.height();
@@ -455,7 +462,7 @@
     public boolean onKey(View v, int keyCode, KeyEvent event) {
         // Clear the frame and give focus to the widget host view when a directional key is pressed.
         if (FocusLogic.shouldConsume(keyCode)) {
-            mDragLayer.clearResizeFrame();
+            close(false);
             mWidgetView.requestFocus();
             return true;
         }
@@ -505,9 +512,25 @@
         if (ev.getAction() == MotionEvent.ACTION_DOWN && handleTouchDown(ev)) {
             return true;
         }
+        close(false);
         return false;
     }
 
+    @Override
+    protected void handleClose(boolean animate) {
+        mDragLayer.removeView(this);
+    }
+
+    @Override
+    public void logActionCommand(int command) {
+        // TODO: Log this case.
+    }
+
+    @Override
+    protected boolean isOfType(int type) {
+        return (type & TYPE_WIDGET_RESIZE_FRAME) != 0;
+    }
+
     /**
      * A mutable class for describing the range of two int values.
      */
diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java
index d82579b..162aa08 100644
--- a/src/com/android/launcher3/AutoInstallsLayout.java
+++ b/src/com/android/launcher3/AutoInstallsLayout.java
@@ -28,7 +28,9 @@
 import android.database.sqlite.SQLiteDatabase;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
+import android.os.Build;
 import android.os.Bundle;
+import android.os.Process;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
@@ -433,8 +435,10 @@
                 return -1;
             }
 
-            mValues.put(LauncherSettings.Favorites.ICON,
-                    Utilities.flattenBitmap(LauncherIcons.createIconBitmap(icon, mContext)));
+            // Auto installs should always support the current platform version.
+            mValues.put(LauncherSettings.Favorites.ICON, Utilities.flattenBitmap(
+                    LauncherIcons.createBadgedIconBitmap(
+                            icon, Process.myUserHandle(), mContext, Build.VERSION.SDK_INT)));
             mValues.put(Favorites.ICON_PACKAGE, mIconRes.getResourcePackageName(iconId));
             mValues.put(Favorites.ICON_RESOURCE, mIconRes.getResourceName(iconId));
 
diff --git a/src/com/android/launcher3/BaseContainerView.java b/src/com/android/launcher3/BaseContainerView.java
deleted file mode 100644
index 82175b7..0000000
--- a/src/com/android/launcher3/BaseContainerView.java
+++ /dev/null
@@ -1,218 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3;
-
-import android.annotation.SuppressLint;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.InsetDrawable;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.widget.FrameLayout;
-
-import com.android.launcher3.allapps.AllAppsContainerView;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.util.TransformingTouchDelegate;
-
-/**
- * A base container view, which supports resizing.
- */
-public abstract class BaseContainerView extends FrameLayout
-        implements DeviceProfile.LauncherLayoutChangeListener {
-
-    private static final Rect sBgPaddingRect = new Rect();
-
-    protected final Drawable mBaseDrawable;
-
-    private View mRevealView;
-    private View mContent;
-
-    private TransformingTouchDelegate mTouchDelegate;
-
-    private final PointF mLastTouchDownPosPx = new PointF(-1.0f, -1.0f);
-
-    public BaseContainerView(Context context) {
-        this(context, null);
-    }
-
-    public BaseContainerView(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public BaseContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-
-        if (this instanceof AllAppsContainerView) {
-            mBaseDrawable = new ColorDrawable();
-        } else {
-            TypedArray a = context.obtainStyledAttributes(attrs,
-                    R.styleable.BaseContainerView, defStyleAttr, 0);
-            mBaseDrawable = a.getDrawable(R.styleable.BaseContainerView_revealBackground);
-            a.recycle();
-        }
-    }
-
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-
-        DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile();
-        grid.addLauncherLayoutChangedListener(this);
-
-        View touchDelegateTargetView = getTouchDelegateTargetView();
-        if (touchDelegateTargetView != null) {
-            mTouchDelegate = new TransformingTouchDelegate(touchDelegateTargetView);
-            ((View) touchDelegateTargetView.getParent()).setTouchDelegate(mTouchDelegate);
-        }
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-
-        DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile();
-        grid.removeLauncherLayoutChangedListener(this);
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-
-        mContent = findViewById(R.id.main_content);
-        mRevealView = findViewById(R.id.reveal_view);
-
-        updatePaddings();
-    }
-
-    @Override
-    public void onLauncherLayoutChanged() {
-        updatePaddings();
-    }
-
-    /**
-     * Calculate the background padding as it can change due to insets/content padding change.
-     */
-    private void updatePaddings() {
-        DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile();
-        int[] padding = grid.getContainerPadding();
-
-        int paddingLeft = padding[0];
-        int paddingRight = padding[1];
-        int paddingTop = 0;
-        int paddingBottom = 0;
-
-        if (!grid.isVerticalBarLayout()) {
-            paddingLeft += grid.edgeMarginPx;
-            paddingRight += grid.edgeMarginPx;
-            paddingTop = paddingBottom = grid.edgeMarginPx;
-        }
-        updateBackground(paddingLeft, paddingTop, paddingRight, paddingBottom);
-    }
-
-    /**
-     * Update the background for the reveal view and content view based on the background padding.
-     */
-    protected void updateBackground(int paddingLeft, int paddingTop,
-            int paddingRight, int paddingBottom) {
-        mRevealView.setBackground(new InsetDrawable(mBaseDrawable,
-                paddingLeft, paddingTop, paddingRight, paddingBottom));
-        mContent.setBackground(new InsetDrawable(mBaseDrawable,
-                paddingLeft, paddingTop, paddingRight, paddingBottom));
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        super.onLayout(changed, left, top, right, bottom);
-
-        View touchDelegateTargetView = getTouchDelegateTargetView();
-        if (touchDelegateTargetView != null) {
-            getRevealView().getBackground().getPadding(sBgPaddingRect);
-            mTouchDelegate.setBounds(
-                    touchDelegateTargetView.getLeft() - sBgPaddingRect.left,
-                    touchDelegateTargetView.getTop() - sBgPaddingRect.top,
-                    touchDelegateTargetView.getRight() + sBgPaddingRect.right,
-                    touchDelegateTargetView.getBottom() + sBgPaddingRect.bottom);
-        }
-    }
-
-    @Override
-    public boolean onInterceptTouchEvent(MotionEvent ev) {
-        return handleTouchEvent(ev);
-    }
-
-    @SuppressLint("ClickableViewAccessibility")
-    @Override
-    public boolean onTouchEvent(MotionEvent ev) {
-        return handleTouchEvent(ev);
-    }
-
-    public void setRevealDrawableColor(int color) {
-        ((ColorDrawable) mBaseDrawable).setColor(color);
-    }
-
-    public final View getContentView() {
-        return mContent;
-    }
-
-    public final View getRevealView() {
-        return mRevealView;
-    }
-
-
-    /**
-     * Handles the touch events that shows the workspace when clicking outside the bounds of the
-     * touch delegate target view.
-     */
-    private boolean handleTouchEvent(MotionEvent ev) {
-        switch (ev.getAction()) {
-            case MotionEvent.ACTION_DOWN:
-                // Check if the touch is outside touch delegate target view
-                View touchDelegateTargetView = getTouchDelegateTargetView();
-                float leftBoundPx = touchDelegateTargetView.getLeft();
-                if (ev.getX() < leftBoundPx ||
-                        ev.getX() > (touchDelegateTargetView.getWidth() + leftBoundPx)) {
-                    mLastTouchDownPosPx.set((int) ev.getX(), (int) ev.getY());
-                }
-                break;
-            case MotionEvent.ACTION_UP:
-                if (mLastTouchDownPosPx.x > -1) {
-                    ViewConfiguration viewConfig = ViewConfiguration.get(getContext());
-                    float dx = ev.getX() - mLastTouchDownPosPx.x;
-                    float dy = ev.getY() - mLastTouchDownPosPx.y;
-                    float distance = PointF.length(dx, dy);
-                    if (distance < viewConfig.getScaledTouchSlop()) {
-                        // The background was clicked, so just go home
-                        Launcher.getLauncher(getContext()).showWorkspace(true);
-                        return true;
-                    }
-                }
-                // Fall through
-            case MotionEvent.ACTION_CANCEL:
-                mLastTouchDownPosPx.set(-1, -1);
-                break;
-        }
-        return false;
-    }
-
-    public abstract View getTouchDelegateTargetView();
-}
diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java
index 3ee6e51..b315980 100644
--- a/src/com/android/launcher3/BaseRecyclerView.java
+++ b/src/com/android/launcher3/BaseRecyclerView.java
@@ -21,6 +21,7 @@
 import android.support.v7.widget.RecyclerView;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
+import android.view.View;
 import android.view.ViewGroup;
 import android.widget.TextView;
 
@@ -60,9 +61,13 @@
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
-        ViewGroup parent = (ViewGroup) getParent();
+        bindFastScrollbar();
+    }
+
+    public void bindFastScrollbar() {
+        ViewGroup parent = (ViewGroup) getParent().getParent();
         mScrollbar = parent.findViewById(R.id.fast_scroller);
-        mScrollbar.setRecyclerView(this, (TextView) parent.findViewById(R.id.fast_scroller_popup));
+        mScrollbar.setRecyclerView(this, parent.findViewById(R.id.fast_scroller_popup));
     }
 
     /**
@@ -99,11 +104,15 @@
         // DO NOT REMOVE, NEEDED IMPLEMENTATION FOR M BUILDS
     }
 
+    public int getScrollBarTop() {
+        return getPaddingTop();
+    }
+
     /**
      * Returns the height of the fast scroll bar
      */
     public int getScrollbarTrackHeight() {
-        return getHeight() - getPaddingTop() - getPaddingBottom();
+        return getHeight() - getScrollBarTop() - getPaddingBottom();
     }
 
     /**
@@ -121,13 +130,6 @@
         return availableScrollBarHeight;
     }
 
-    /**
-     * Returns the scrollbar for this recycler view.
-     */
-    public RecyclerViewFastScroller getScrollBar() {
-        return mScrollbar;
-    }
-
     @Override
     protected void dispatchDraw(Canvas canvas) {
         onUpdateScrollbar(0);
@@ -160,6 +162,28 @@
     }
 
     /**
+     * Returns whether the view itself will handle the touch event or not.
+     * @param ev MotionEvent in {@param eventSource}
+     */
+    public boolean shouldContainerScroll(MotionEvent ev, View eventSource) {
+        int[] point = new int[2];
+        point[0] = (int) ev.getX();
+        point[1] = (int) ev.getY();
+        Utilities.mapCoordInSelfToDescendant(mScrollbar, eventSource, point);
+        // IF the MotionEvent is inside the thumb, container should not be pulled down.
+        if (mScrollbar.shouldBlockIntercept(point[0], point[1])) {
+            return false;
+        }
+
+        // IF scroller is at the very top OR there is no scroll bar because there is probably not
+        // enough items to scroll, THEN it's okay for the container to be pulled down.
+        if (getCurrentScrollY() == 0) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
      * @return whether fast scrolling is supported in the current state.
      */
     public boolean supportsFastScrolling() {
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index ac842f9..a590504 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -42,6 +42,7 @@
 
 import com.android.launcher3.IconCache.IconLoadRequest;
 import com.android.launcher3.IconCache.ItemInfoUpdateReceiver;
+import com.android.launcher3.Launcher.OnResumeCallback;
 import com.android.launcher3.badge.BadgeInfo;
 import com.android.launcher3.badge.BadgeRenderer;
 import com.android.launcher3.folder.FolderIcon;
@@ -59,7 +60,7 @@
  * because we want to make the bubble taller than the text and TextView's clip is
  * too aggressive.
  */
-public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver {
+public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, OnResumeCallback {
 
     private static final int DISPLAY_WORKSPACE = 0;
     private static final int DISPLAY_ALL_APPS = 1;
@@ -177,6 +178,16 @@
 
     }
 
+    /**
+     * Resets the view so it can be recycled.
+     */
+    public void reset() {
+        mBadgeInfo = null;
+        mBadgePalette = null;
+        mBadgeScale = 0f;
+        mForceHideBadge = false;
+    }
+
     public void applyFromShortcutInfo(ShortcutInfo info) {
         applyFromShortcutInfo(info, false);
     }
@@ -330,6 +341,13 @@
         refreshDrawableState();
     }
 
+    @Override
+    public void onLauncherResume() {
+        // Reset the pressed state of icon that was locked in the press state while activity
+        // was launching
+        setStayPressed(false);
+    }
+
     void clearPressedBackground() {
         setPressed(false);
         setStayPressed(false);
@@ -496,11 +514,17 @@
     public PreloadIconDrawable applyProgressLevel(int progressLevel) {
         if (getTag() instanceof ItemInfoWithIcon) {
             ItemInfoWithIcon info = (ItemInfoWithIcon) getTag();
-            setContentDescription(progressLevel > 0
-                    ? getContext().getString(R.string.app_downloading_title, info.title,
-                    NumberFormat.getPercentInstance().format(progressLevel * 0.01))
-                    : getContext().getString(R.string.app_waiting_download_title, info.title));
-
+            if (progressLevel >= 100) {
+                setContentDescription(info.contentDescription != null
+                        ? info.contentDescription : "");
+            } else if (progressLevel > 0) {
+                setContentDescription(getContext()
+                        .getString(R.string.app_downloading_title, info.title,
+                                NumberFormat.getPercentInstance().format(progressLevel * 0.01)));
+            } else {
+                setContentDescription(getContext()
+                        .getString(R.string.app_waiting_download_title, info.title));
+            }
             if (mIcon != null) {
                 final PreloadIconDrawable preloadDrawable;
                 if (mIcon instanceof PreloadIconDrawable) {
diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java
index 632e490..cfb55cc 100644
--- a/src/com/android/launcher3/ButtonDropTarget.java
+++ b/src/com/android/launcher3/ButtonDropTarget.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3;
 
+import static com.android.launcher3.LauncherState.NORMAL;
+
 import android.animation.AnimatorSet;
 import android.animation.FloatArrayEvaluator;
 import android.animation.ObjectAnimator;
@@ -35,10 +37,9 @@
 import android.view.View.OnClickListener;
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
-import android.view.animation.DecelerateInterpolator;
-import android.view.animation.LinearInterpolator;
 import android.widget.TextView;
 
+import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragOptions;
@@ -52,6 +53,7 @@
 public abstract class ButtonDropTarget extends TextView
         implements DropTarget, DragController.DragListener, OnClickListener {
 
+    private static final int[] sTempCords = new int[2];
     private static final int DRAG_VIEW_DROP_DURATION = 285;
 
     private final boolean mHideParentOnDisable;
@@ -178,7 +180,7 @@
 
     @Override
     public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
-        mActive = supportsDrop(dragObject.dragSource, dragObject.dragInfo);
+        mActive = supportsDrop(dragObject.dragInfo);
         mDrawable.setColorFilter(null);
         if (mCurrentColorAnim != null) {
             mCurrentColorAnim.cancel();
@@ -194,10 +196,14 @@
 
     @Override
     public final boolean acceptDrop(DragObject dragObject) {
-        return supportsDrop(dragObject.dragSource, dragObject.dragInfo);
+        return supportsDrop(dragObject.dragInfo);
     }
 
-    protected abstract boolean supportsDrop(DragSource source, ItemInfo info);
+    protected abstract boolean supportsDrop(ItemInfo info);
+
+    public boolean supportsAccessibilityDrop(ItemInfo info) {
+        return supportsDrop(info);
+    }
 
     @Override
     public boolean isDropEnabled() {
@@ -215,7 +221,7 @@
      * On drop animate the dropView to the icon.
      */
     @Override
-    public void onDrop(final DragObject d) {
+    public void onDrop(final DragObject d, final DragOptions options) {
         final DragLayer dragLayer = mLauncher.getDragLayer();
         final Rect from = new Rect();
         dragLayer.getViewRectRelativeToSelf(d.dragView, from);
@@ -229,19 +235,22 @@
             public void run() {
                 completeDrop(d);
                 mDropTargetBar.onDragEnd();
-                mLauncher.exitSpringLoadedDragModeDelayed(true, 0, null);
+                mLauncher.getStateManager().goToState(NORMAL);
             }
         };
         dragLayer.animateView(d.dragView, from, to, scale, 1f, 1f, 0.1f, 0.1f,
                 DRAG_VIEW_DROP_DURATION,
-                new DecelerateInterpolator(2),
-                new LinearInterpolator(), onAnimationEndRunnable,
+                Interpolators.DEACCEL_2, Interpolators.LINEAR, onAnimationEndRunnable,
                 DragLayer.ANIMATION_END_DISAPPEAR, null);
     }
 
+    public abstract int getAccessibilityAction();
+
     @Override
     public void prepareAccessibilityDrop() { }
 
+    public abstract void onAccessibilityDrop(View view, ItemInfo item);
+
     public abstract void completeDrop(DragObject d);
 
     @Override
@@ -249,9 +258,9 @@
         super.getHitRect(outRect);
         outRect.bottom += mBottomDragPadding;
 
-        int[] coords = new int[2];
-        mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, coords);
-        outRect.offsetTo(coords[0], coords[1]);
+        sTempCords[0] = sTempCords[1] = 0;
+        mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, sTempCords);
+        outRect.offsetTo(sTempCords[0], sTempCords[1]);
     }
 
     public Rect getIconRect(DragObject dragObject) {
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index a75c6d1..79a34a0 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -44,15 +44,15 @@
 import android.view.ViewDebug;
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
-import android.view.animation.DecelerateInterpolator;
+
 import com.android.launcher3.BubbleTextView.BubbleTextShadowHandler;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate;
 import com.android.launcher3.accessibility.FolderAccessibilityHelper;
 import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
+import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.anim.PropertyListBuilder;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.folder.PreviewBackground;
 import com.android.launcher3.graphics.DragPreviewProvider;
 import com.android.launcher3.util.CellAndSpan;
@@ -60,6 +60,7 @@
 import com.android.launcher3.util.ParcelableSparseArray;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.Thunk;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
@@ -89,8 +90,6 @@
     private int mCountY;
 
     private boolean mDropPending = false;
-    private boolean mIsDragTarget = true;
-    private boolean mJailContent = true;
 
     // These are temporary variables to prevent having to allocate a new object just to
     // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
@@ -106,8 +105,6 @@
     private final ArrayList<PreviewBackground> mFolderBackgrounds = new ArrayList<>();
     final PreviewBackground mFolderLeaveBehind = new PreviewBackground();
 
-    private float mBackgroundAlpha;
-
     private static final int[] BACKGROUND_STATE_ACTIVE = new int[] { android.R.attr.state_active };
     private static final int[] BACKGROUND_STATE_DEFAULT = new int[0];
     private final Drawable mBackground;
@@ -223,12 +220,12 @@
 
         mBackground = res.getDrawable(R.drawable.bg_celllayout);
         mBackground.setCallback(this);
-        mBackground.setAlpha((int) (mBackgroundAlpha * 255));
+        mBackground.setAlpha(0);
 
         mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE * grid.iconSizePx);
 
         // Initialize the data structures used for the drag visualization.
-        mEaseOutInterpolator = new DecelerateInterpolator(2.5f); // Quint ease out
+        mEaseOutInterpolator = Interpolators.DEACCEL_2_5; // Quint ease out
         mDragCell[0] = mDragCell[1] = -1;
         for (int i = 0; i < mDragOutlines.length; i++) {
             mDragOutlines[i] = new Rect(-1, -1, -1, -1);
@@ -346,7 +343,7 @@
         // the home screen mode, however, once in overview mode stylus button press should be
         // enabled to allow rearranging the different home screens. So check what mode
         // the workspace is in, and only perform stylus button presses while in overview mode.
-        if (mLauncher.mWorkspace.isInOverviewMode()
+        if (mLauncher.isInState(LauncherState.OVERVIEW)
                 && mStylusEventHelper.onMotionEvent(ev)) {
             return true;
         }
@@ -357,10 +354,6 @@
         mShortcutsAndWidgets.setLayerType(hasLayer ? LAYER_TYPE_HARDWARE : LAYER_TYPE_NONE, sPaint);
     }
 
-    public void buildHardwareLayer() {
-        mShortcutsAndWidgets.buildLayer();
-    }
-
     public void setCellDimensions(int width, int height) {
         mFixedCellWidth = mCellWidth = width;
         mFixedCellHeight = mCellHeight = height;
@@ -392,24 +385,7 @@
 
     @Override
     public void setPressedIcon(BubbleTextView icon, Bitmap background) {
-        if (icon == null || background == null) {
-            mTouchFeedbackView.setBitmap(null);
-            mTouchFeedbackView.animate().cancel();
-        } else {
-            if (mTouchFeedbackView.setBitmap(background)) {
-                mTouchFeedbackView.alignWithIconView(icon, mShortcutsAndWidgets,
-                        null /* clipAgainstView */);
-                mTouchFeedbackView.animateShadow();
-            }
-        }
-    }
-
-    void disableDragTarget() {
-        mIsDragTarget = false;
-    }
-
-    public boolean isDragTarget() {
-        return mIsDragTarget;
+        mTouchFeedbackView.setPressedIcon(icon, background);
     }
 
     void setIsDragOverlapping(boolean isDragOverlapping) {
@@ -421,26 +397,22 @@
         }
     }
 
-    public void disableJailContent() {
-        mJailContent = false;
-    }
-
     @Override
     protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
-        if (mJailContent) {
-            ParcelableSparseArray jail = getJailedArray(container);
-            super.dispatchSaveInstanceState(jail);
-            container.put(R.id.cell_layout_jail_id, jail);
-        } else {
-            super.dispatchSaveInstanceState(container);
-        }
+        ParcelableSparseArray jail = getJailedArray(container);
+        super.dispatchSaveInstanceState(jail);
+        container.put(R.id.cell_layout_jail_id, jail);
     }
 
     @Override
     protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
-        super.dispatchRestoreInstanceState(mJailContent ? getJailedArray(container) : container);
+        super.dispatchRestoreInstanceState(getJailedArray(container));
     }
 
+    /**
+     * Wrap the SparseArray in another Parcelable so that the item ids do not conflict with our
+     * our internal resource ids
+     */
     private ParcelableSparseArray getJailedArray(SparseArray<Parcelable> container) {
         final Parcelable parcelable = container.get(R.id.cell_layout_jail_id);
         return parcelable instanceof ParcelableSparseArray ?
@@ -453,16 +425,12 @@
 
     @Override
     protected void onDraw(Canvas canvas) {
-        if (!mIsDragTarget) {
-            return;
-        }
-
         // When we're large, we are either drawn in a "hover" state (ie when dragging an item to
         // a neighboring page) or with just a normal background (if backgroundAlpha > 0.0f)
         // When we're small, we are either drawn normally or in the "accepts drops" state (during
         // a drag). However, we also drag the mini hover background *over* one of those two
         // backgrounds
-        if (mBackgroundAlpha > 0.0f) {
+        if (mBackground.getAlpha() > 0) {
             mBackground.draw(canvas);
         }
 
@@ -838,16 +806,10 @@
 
     @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
-        boolean isFullscreen = mShortcutsAndWidgets.getChildCount() > 0 &&
-                ((LayoutParams) mShortcutsAndWidgets.getChildAt(0).getLayoutParams()).isFullscreen;
         int left = getPaddingLeft();
-        if (!isFullscreen) {
-            left += (int) Math.ceil(getUnusedHorizontalSpace() / 2f);
-        }
+        left += (int) Math.ceil(getUnusedHorizontalSpace() / 2f);
         int right = r - l - getPaddingRight();
-        if (!isFullscreen) {
-            right -= (int) Math.ceil(getUnusedHorizontalSpace() / 2f);
-        }
+        right -= (int) Math.ceil(getUnusedHorizontalSpace() / 2f);
 
         int top = getPaddingTop();
         int bottom = b - t - getPaddingBottom();
@@ -875,20 +837,13 @@
         return getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - (mCountX * mCellWidth);
     }
 
-    public float getBackgroundAlpha() {
-        return mBackgroundAlpha;
-    }
-
-    public void setBackgroundAlpha(float alpha) {
-        if (mBackgroundAlpha != alpha) {
-            mBackgroundAlpha = alpha;
-            mBackground.setAlpha((int) (mBackgroundAlpha * 255));
-        }
+    public Drawable getScrimBackground() {
+        return mBackground;
     }
 
     @Override
     protected boolean verifyDrawable(Drawable who) {
-        return super.verifyDrawable(who) || (mIsDragTarget && who == mBackground);
+        return super.verifyDrawable(who) || (who == mBackground);
     }
 
     public void setShortcutAndWidgetAlpha(float alpha) {
@@ -2654,11 +2609,6 @@
         public boolean isLockedToGrid = true;
 
         /**
-         * Indicates that this item should use the full extents of its parent.
-         */
-        public boolean isFullscreen = false;
-
-        /**
          * Indicates whether this item can be reordered. Always true except in the case of the
          * the AllApps button and QSB place holder.
          */
diff --git a/src/com/android/launcher3/ClickShadowView.java b/src/com/android/launcher3/ClickShadowView.java
index aad1112..5391b4d 100644
--- a/src/com/android/launcher3/ClickShadowView.java
+++ b/src/com/android/launcher3/ClickShadowView.java
@@ -16,15 +16,28 @@
 
 package com.android.launcher3;
 
+import static com.android.launcher3.FastBitmapDrawable.CLICK_FEEDBACK_DURATION;
+import static com.android.launcher3.FastBitmapDrawable.CLICK_FEEDBACK_INTERPOLATOR;
+import static com.android.launcher3.LauncherAnimUtils.ELEVATION;
+import static com.android.launcher3.graphics.HolographicOutlineHelper.ADAPTIVE_ICON_SHADOW_BITMAP;
+
+import android.animation.ObjectAnimator;
+import android.annotation.TargetApi;
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
+import android.graphics.Outline;
 import android.graphics.Paint;
 import android.graphics.Rect;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.util.Property;
 import android.view.View;
 import android.view.ViewDebug;
 import android.view.ViewGroup;
+import android.view.ViewOutlineProvider;
 
 public class ClickShadowView extends View {
 
@@ -32,6 +45,8 @@
     private static final int SHADOW_LOW_ALPHA = 30;
     private static final int SHADOW_HIGH_ALPHA = 60;
 
+    private static float sAdaptiveIconScaleFactor = 1f;
+
     private final Paint mPaint;
 
     @ViewDebug.ExportedProperty(category = "launcher")
@@ -40,6 +55,10 @@
     private final float mShadowPadding;
 
     private Bitmap mBitmap;
+    private ObjectAnimator mAnim;
+
+    private Drawable mAdaptiveIcon;
+    private ViewOutlineProvider mOutlineProvider;
 
     public ClickShadowView(Context context) {
         super(context);
@@ -50,6 +69,10 @@
         mShadowOffset = getResources().getDimension(R.dimen.click_shadow_high_shift);
     }
 
+    public static void setAdaptiveIconScaleFactor(float factor) {
+        sAdaptiveIconScaleFactor = factor;
+    }
+
     /**
      * @return extra space required by the view to show the shadow.
      */
@@ -57,11 +80,64 @@
         return (int) (SHADOW_SIZE_FACTOR * mShadowPadding);
     }
 
+    public void setPressedIcon(BubbleTextView icon, Bitmap background) {
+        if (icon == null) {
+            setBitmap(null);
+            cancelAnim();
+            return;
+        }
+        if (background == null) {
+            if (mBitmap == ADAPTIVE_ICON_SHADOW_BITMAP) {
+                // clear animation shadow
+            }
+            setBitmap(null);
+            cancelAnim();
+            icon.setOutlineProvider(null);
+        } else if (setBitmap(background)) {
+            if (mBitmap == ADAPTIVE_ICON_SHADOW_BITMAP) {
+                setupAdaptiveShadow(icon);
+                cancelAnim();
+                startAnim(icon, ELEVATION,
+                        getResources().getDimension(R.dimen.click_shadow_elevation));
+            } else {
+                alignWithIconView(icon);
+                startAnim(this, ALPHA, 1);
+            }
+        }
+    }
+
+    @TargetApi(Build.VERSION_CODES.O)
+    private void setupAdaptiveShadow(final BubbleTextView view) {
+        if (mAdaptiveIcon == null) {
+            mAdaptiveIcon = new AdaptiveIconDrawable(null, null);
+            mOutlineProvider = new ViewOutlineProvider() {
+                @Override
+                public void getOutline(View view, Outline outline) {
+                    mAdaptiveIcon.getOutline(outline);
+                }
+            };
+        }
+
+        int iconWidth = view.getRight() - view.getLeft();
+        int iconHSpace = iconWidth - view.getCompoundPaddingRight() - view.getCompoundPaddingLeft();
+        int drawableWidth = view.getIcon().getBounds().width();
+
+        Rect bounds = new Rect();
+        bounds.left = view.getCompoundPaddingLeft() + (iconHSpace - drawableWidth) / 2;
+        bounds.right = bounds.left + drawableWidth;
+        bounds.top = view.getPaddingTop();
+        bounds.bottom = bounds.top + view.getIcon().getBounds().height();
+        Utilities.scaleRectAboutCenter(bounds, sAdaptiveIconScaleFactor);
+
+        mAdaptiveIcon.setBounds(bounds);
+        view.setOutlineProvider(mOutlineProvider);
+    }
+
     /**
      * Applies the new bitmap.
      * @return true if the view was invalidated.
      */
-    public boolean setBitmap(Bitmap b) {
+    private boolean setBitmap(Bitmap b) {
         if (b != mBitmap){
             mBitmap = b;
             invalidate();
@@ -80,48 +156,51 @@
         }
     }
 
-    public void animateShadow() {
-        setAlpha(0);
-        animate().alpha(1)
-            .setDuration(FastBitmapDrawable.CLICK_FEEDBACK_DURATION)
-            .setInterpolator(FastBitmapDrawable.CLICK_FEEDBACK_INTERPOLATOR)
-            .start();
+    private void cancelAnim() {
+        if (mAnim != null) {
+            mAnim.cancel();
+            mAnim.setCurrentPlayTime(0);
+            mAnim = null;
+        }
+    }
+
+    private void startAnim(View target, Property<View, Float> property, float endValue) {
+        cancelAnim();
+        property.set(target, 0f);
+        mAnim = ObjectAnimator.ofFloat(target, property, endValue);
+        mAnim.setDuration(CLICK_FEEDBACK_DURATION)
+                .setInterpolator(CLICK_FEEDBACK_INTERPOLATOR);
+        mAnim.start();
     }
 
     /**
      * Aligns the shadow with {@param view}
-     * @param viewParent immediate parent of {@param view}. It must be a sibling of this view.
+     * Note: {@param view} must be a descendant of my parent.
      */
-    public void alignWithIconView(BubbleTextView view, ViewGroup viewParent, View clipAgainstView) {
-        float leftShift = view.getLeft() + viewParent.getLeft() - getLeft();
-        float topShift = view.getTop() + viewParent.getTop() - getTop();
+    private void alignWithIconView(BubbleTextView view) {
+        int[] coords = new int[] {0, 0};
+        Utilities.getDescendantCoordRelativeToAncestor(
+                (ViewGroup) view.getParent(), (View) getParent(), coords, false);
+
+        float leftShift = view.getLeft() + coords[0] - getLeft();
+        float topShift = view.getTop() + coords[1] - getTop();
         int iconWidth = view.getRight() - view.getLeft();
         int iconHeight = view.getBottom() - view.getTop();
         int iconHSpace = iconWidth - view.getCompoundPaddingRight() - view.getCompoundPaddingLeft();
         float drawableWidth = view.getIcon().getBounds().width();
 
-        if (clipAgainstView != null) {
-            // Set the bounds to clip against
-            int[] coords = new int[] {0, 0};
-            Utilities.getDescendantCoordRelativeToAncestor(clipAgainstView, (View) getParent(),
-                    coords, false);
-            int clipLeft = (int) Math.max(0, coords[0] - leftShift - mShadowPadding);
-            int clipTop = (int) Math.max(0, coords[1] - topShift - mShadowPadding) ;
-            setClipBounds(new Rect(clipLeft, clipTop, clipLeft + iconWidth, clipTop + iconHeight));
-        } else {
-            // Reset the clip bounds
-            setClipBounds(null);
-        }
+        // Set the bounds to clip against
+        int clipLeft = (int) Math.max(0, coords[0] - leftShift - mShadowPadding);
+        int clipTop = (int) Math.max(0, coords[1] - topShift - mShadowPadding) ;
+        setClipBounds(new Rect(clipLeft, clipTop, clipLeft + iconWidth, clipTop + iconHeight));
 
         setTranslationX(leftShift
-                + viewParent.getTranslationX()
                 + view.getCompoundPaddingLeft() * view.getScaleX()
                 + (iconHSpace - drawableWidth) * view.getScaleX() / 2  /* drawable gap */
                 + iconWidth * (1 - view.getScaleX()) / 2  /* gap due to scale */
                 - mShadowPadding  /* extra shadow size */
                 );
         setTranslationY(topShift
-                + viewParent.getTranslationY()
                 + view.getPaddingTop() * view.getScaleY()  /* drawable gap */
                 + view.getHeight() * (1 - view.getScaleY()) / 2  /* gap due to scale */
                 - mShadowPadding  /* extra shadow size */
diff --git a/src/com/android/launcher3/CustomAppWidget.java b/src/com/android/launcher3/CustomAppWidget.java
deleted file mode 100644
index 1b4ed79..0000000
--- a/src/com/android/launcher3/CustomAppWidget.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package com.android.launcher3;
-
-public interface CustomAppWidget {
-    public String getLabel();
-    public int getPreviewImage();
-    public int getIcon();
-    public int getWidgetLayout();
-
-    public int getSpanX();
-    public int getSpanY();
-    public int getMinSpanX();
-    public int getMinSpanY();
-    public int getResizeMode();
-}
diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java
index 4dcb64f..c12ea57 100644
--- a/src/com/android/launcher3/DeleteDropTarget.java
+++ b/src/com/android/launcher3/DeleteDropTarget.java
@@ -21,6 +21,7 @@
 import android.util.AttributeSet;
 import android.view.View;
 
+import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.folder.Folder;
 
@@ -46,27 +47,35 @@
     @Override
     public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
         super.onDragStart(dragObject, options);
-        setTextBasedOnDragSource(dragObject.dragSource);
+        setTextBasedOnDragSource(dragObject.dragInfo);
     }
 
-    /** @return true for items that should have a "Remove" action in accessibility. */
-    public static boolean supportsAccessibleDrop(ItemInfo info) {
+    /**
+     * @return true for items that should have a "Remove" action in accessibility.
+     */
+    @Override
+    public boolean supportsAccessibilityDrop(ItemInfo info) {
         return (info instanceof ShortcutInfo)
                 || (info instanceof LauncherAppWidgetInfo)
                 || (info instanceof FolderInfo);
     }
 
     @Override
-    protected boolean supportsDrop(DragSource source, ItemInfo info) {
+    public int getAccessibilityAction() {
+        return LauncherAccessibilityDelegate.REMOVE;
+    }
+
+    @Override
+    protected boolean supportsDrop(ItemInfo info) {
         return true;
     }
 
     /**
-     * Set the drop target's text to either "Remove" or "Cancel" depending on the drag source.
+     * Set the drop target's text to either "Remove" or "Cancel" depending on the drag item.
      */
-    public void setTextBasedOnDragSource(DragSource dragSource) {
+    private void setTextBasedOnDragSource(ItemInfo item) {
         if (!TextUtils.isEmpty(mText)) {
-            mText = getResources().getString(dragSource.supportsDeleteDropTarget()
+            mText = getResources().getString(item.id != ItemInfo.NO_ID
                     ? R.string.remove_drop_target_label
                     : android.R.string.cancel);
             requestLayout();
@@ -77,19 +86,21 @@
     public void completeDrop(DragObject d) {
         ItemInfo item = d.dragInfo;
         if ((d.dragSource instanceof Workspace) || (d.dragSource instanceof Folder)) {
-            removeWorkspaceOrFolderItem(mLauncher, item, null);
+            onAccessibilityDrop(null, item);
         }
     }
 
     /**
      * Removes the item from the workspace. If the view is not null, it also removes the view.
      */
-    public static void removeWorkspaceOrFolderItem(Launcher launcher, ItemInfo item, View view) {
+    @Override
+    public void onAccessibilityDrop(View view, ItemInfo item) {
         // Remove the item from launcher and the db, we can ignore the containerInfo in this call
         // because we already remove the drag view from the folder (if the drag originated from
         // a folder) in Folder.beginDrag()
-        launcher.removeItem(view, item, true /* deleteFromDb */);
-        launcher.getWorkspace().stripEmptyScreens();
-        launcher.getDragLayer().announceForAccessibility(launcher.getString(R.string.item_removed));
+        mLauncher.removeItem(view, item, true /* deleteFromDb */);
+        mLauncher.getWorkspace().stripEmptyScreens();
+        mLauncher.getDragLayer()
+                .announceForAccessibility(getContext().getString(R.string.item_removed));
     }
 }
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 69ee03e..c6226f4 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -27,11 +27,11 @@
 import android.util.DisplayMetrics;
 import android.view.Gravity;
 import android.view.View;
-import android.view.ViewGroup;
 import android.view.ViewGroup.LayoutParams;
 import android.widget.FrameLayout;
 
 import com.android.launcher3.CellLayout.ContainerType;
+import com.android.launcher3.allapps.AllAppsContainerView;
 import com.android.launcher3.badge.BadgeRenderer;
 
 import java.util.ArrayList;
@@ -65,12 +65,6 @@
 
     private static final float TALL_DEVICE_ASPECT_RATIO_THRESHOLD = 2.0f;
 
-    // Overview mode
-    private final int overviewModeMinIconZoneHeightPx;
-    private final int overviewModeMaxIconZoneHeightPx;
-    private final int overviewModeBarItemWidthPx;
-    private final int overviewModeBarSpacerWidthPx;
-    private final float overviewModeIconZoneRatio;
 
     // Workspace
     private final int desiredWorkspaceLeftRightMarginPx;
@@ -196,16 +190,6 @@
                 res.getDimensionPixelSize(R.dimen.dynamic_grid_workspace_page_spacing);
         topWorkspacePadding =
                 res.getDimensionPixelSize(R.dimen.dynamic_grid_workspace_top_padding);
-        overviewModeMinIconZoneHeightPx =
-                res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_min_icon_zone_height);
-        overviewModeMaxIconZoneHeightPx =
-                res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_max_icon_zone_height);
-        overviewModeBarItemWidthPx =
-                res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_item_width);
-        overviewModeBarSpacerWidthPx =
-                res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_spacer_width);
-        overviewModeIconZoneRatio =
-                res.getInteger(R.integer.config_dynamic_grid_overview_icon_zone_percentage) / 100f;
         iconDrawablePaddingOriginalPx =
                 res.getDimensionPixelSize(R.dimen.dynamic_grid_icon_drawable_padding);
         dropTargetBarSizePx = res.getDimensionPixelSize(R.dimen.dynamic_grid_drop_target_size);
@@ -277,8 +261,12 @@
         DeviceProfile profile = new DeviceProfile(context, inv, mwSize, mwSize, mwSize.x, mwSize.y,
                 isLandscape);
 
-        // Hide labels on the workspace.
-        profile.adjustToHideWorkspaceLabels();
+        // If there isn't enough vertical cell padding with the labels displayed, hide the labels.
+        float workspaceCellPaddingY = profile.getCellSize().y - profile.iconSizePx
+                - iconDrawablePaddingPx - profile.iconTextSizePx;
+        if (workspaceCellPaddingY < profile.iconDrawablePaddingPx * 2) {
+            profile.adjustToHideWorkspaceLabels();
+        }
 
         // We use these scales to measure and layout the widgets using their full invariant profile
         // sizes and then draw them scaled and centered to fit in their multi-window mode cellspans.
@@ -566,13 +554,6 @@
         }
     }
 
-    int getOverviewModeButtonBarHeight() {
-        int zoneHeight = (int) (overviewModeIconZoneRatio * availableHeightPx);
-        return Utilities.boundToRange(zoneHeight,
-                overviewModeMinIconZoneHeightPx,
-                overviewModeMaxIconZoneHeightPx);
-    }
-
     public static int calculateCellWidth(int width, int countX) {
         return width / countX;
     }
@@ -593,16 +574,6 @@
         return isVerticalBarLayout() || isLargeTablet;
     }
 
-    private int getVisibleChildCount(ViewGroup parent) {
-        int visibleChildren = 0;
-        for (int i = 0; i < parent.getChildCount(); i++) {
-            if (parent.getChildAt(i).getVisibility() != View.GONE) {
-                visibleChildren++;
-            }
-        }
-        return visibleChildren;
-    }
-
     public void layout(Launcher launcher, boolean notifyListeners) {
         FrameLayout.LayoutParams lp;
         boolean hasVerticalBarLayout = isVerticalBarLayout();
@@ -697,25 +668,10 @@
             pageIndicator.setLayoutParams(lp);
         }
 
-        // Layout the Overview Mode
-        ViewGroup overviewMode = launcher.getOverviewPanel();
-        if (overviewMode != null) {
-            int visibleChildCount = getVisibleChildCount(overviewMode);
-            int totalItemWidth = visibleChildCount * overviewModeBarItemWidthPx;
-            int maxWidth = totalItemWidth + (visibleChildCount - 1) * overviewModeBarSpacerWidthPx;
-
-            lp = (FrameLayout.LayoutParams) overviewMode.getLayoutParams();
-            lp.width = Math.min(availableWidthPx, maxWidth);
-            lp.height = getOverviewModeButtonBarHeight();
-            lp.bottomMargin = mInsets.bottom;
-            overviewMode.setLayoutParams(lp);
-        }
-
         // Layout the AllAppsRecyclerView
-        View view = launcher.findViewById(R.id.apps_list_view);
+        AllAppsContainerView appsView = launcher.findViewById(R.id.apps_view);
         int paddingLeftRight = desiredWorkspaceLeftRightMarginPx + cellLayoutPaddingLeftRightPx;
-        view.setPadding(paddingLeftRight, view.getPaddingTop(), paddingLeftRight,
-                view.getPaddingBottom());
+        appsView.setRecyclerViewSidePadding(paddingLeftRight, paddingLeftRight);
 
         if (notifyListeners) {
             for (int i = mListeners.size() - 1; i >= 0; i--) {
@@ -724,13 +680,13 @@
         }
     }
 
-    private int getCurrentWidth() {
+    public int getCurrentWidth() {
         return isLandscape
                 ? Math.max(widthPx, heightPx)
                 : Math.min(widthPx, heightPx);
     }
 
-    private int getCurrentHeight() {
+    public int getCurrentHeight() {
         return isLandscape
                 ? Math.min(widthPx, heightPx)
                 : Math.max(widthPx, heightPx);
diff --git a/src/com/android/launcher3/DragSource.java b/src/com/android/launcher3/DragSource.java
index dcd8f58..93f865c 100644
--- a/src/com/android/launcher3/DragSource.java
+++ b/src/com/android/launcher3/DragSource.java
@@ -27,24 +27,8 @@
 public interface DragSource extends LogContainerProvider {
 
     /**
-     * @return whether items dragged from this source supports 'App Info'
-     */
-    boolean supportsAppInfoDropTarget();
-
-    /**
-     * @return whether items dragged from this source supports 'Delete' drop target (e.g. to remove
-     * a shortcut.) If this returns false, the drop target will say "Cancel" instead of "Remove."
-     */
-    boolean supportsDeleteDropTarget();
-
-    /*
-     * @return the scale of the icons over the workspace icon size
-     */
-    float getIntrinsicIconScaleFactor();
-
-    /**
      * A callback made back to the source after an item from this source has been dropped on a
      * DropTarget.
      */
-    void onDropCompleted(View target, DragObject d, boolean isFlingToDelete, boolean success);
+    void onDropCompleted(View target, DragObject d, boolean success);
 }
diff --git a/src/com/android/launcher3/DropTarget.java b/src/com/android/launcher3/DropTarget.java
index 7d047d7..4d30479 100644
--- a/src/com/android/launcher3/DropTarget.java
+++ b/src/com/android/launcher3/DropTarget.java
@@ -19,6 +19,7 @@
 import android.graphics.Rect;
 
 import com.android.launcher3.accessibility.DragViewStateAnnouncer;
+import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.dragndrop.DragView;
 
 /**
@@ -58,9 +59,6 @@
         /** The object is part of an accessible drag operation */
         public boolean accessibleDrag;
 
-        /** Post drag animation runnable */
-        public Runnable postAnimationRunnable = null;
-
         /** Indicates that the drag operation was cancelled */
         public boolean cancelled = false;
 
@@ -104,9 +102,16 @@
     boolean isDropEnabled();
 
     /**
-     * Handle an object being dropped on the DropTarget
+     * Handle an object being dropped on the DropTarget.
+     *
+     * This will be called only if this target previously returned true for {@link #acceptDrop}. It
+     * is the responsibility of this target to exit out of the spring loaded mode (either
+     * immediately or after any pending animations).
+     *
+     * If the drop was cancelled for some reason, onDrop will never get called, the UI will
+     * automatically exit out of this mode.
      */
-    void onDrop(DragObject dragObject);
+    void onDrop(DragObject dragObject, DragOptions options);
 
     void onDragEnter(DragObject dragObject);
 
diff --git a/src/com/android/launcher3/DropTargetBar.java b/src/com/android/launcher3/DropTargetBar.java
index 29a1349..3eca5cd 100644
--- a/src/com/android/launcher3/DropTargetBar.java
+++ b/src/com/android/launcher3/DropTargetBar.java
@@ -16,6 +16,9 @@
 
 package com.android.launcher3;
 
+import static com.android.launcher3.AlphaUpdateListener.updateVisibility;
+import static com.android.launcher3.Utilities.isAccessibilityEnabled;
+
 import android.animation.TimeInterpolator;
 import android.content.Context;
 import android.util.AttributeSet;
@@ -23,29 +26,27 @@
 import android.view.ViewDebug;
 import android.view.ViewGroup;
 import android.view.ViewPropertyAnimator;
-import android.view.accessibility.AccessibilityManager;
-import android.view.animation.AccelerateInterpolator;
 import android.widget.LinearLayout;
 
+import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragOptions;
 
+import java.util.ArrayList;
+
 /*
  * The top bar containing various drop targets: Delete/App Info/Uninstall.
  */
 public class DropTargetBar extends LinearLayout implements DragController.DragListener {
 
     protected static final int DEFAULT_DRAG_FADE_DURATION = 175;
-    protected static final TimeInterpolator DEFAULT_INTERPOLATOR = new AccelerateInterpolator();
+    protected static final TimeInterpolator DEFAULT_INTERPOLATOR = Interpolators.ACCEL;
 
     private final Runnable mFadeAnimationEndRunnable = new Runnable() {
 
         @Override
         public void run() {
-            AccessibilityManager am = (AccessibilityManager)
-                    getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
-            boolean accessibilityEnabled = am.isEnabled();
-            AlphaUpdateListener.updateVisibility(DropTargetBar.this, accessibilityEnabled);
+            updateVisibility(DropTargetBar.this, isAccessibilityEnabled(getContext()));
         }
     };
 
@@ -55,6 +56,7 @@
     @ViewDebug.ExportedProperty(category = "launcher")
     protected boolean mVisible = false;
 
+    private ButtonDropTarget[] mDropTargets;
     private ViewPropertyAnimator mCurrentAnimation;
 
     public DropTargetBar(Context context, AttributeSet attrs) {
@@ -75,7 +77,27 @@
 
     public void setup(DragController dragController) {
         dragController.addDragListener(this);
-        setupButtonDropTarget(this, dragController);
+        ArrayList<ButtonDropTarget> outList = new ArrayList<>();
+        findDropTargets(this, outList);
+
+        mDropTargets = new ButtonDropTarget[outList.size()];
+        for (int i = 0; i < mDropTargets.length; i++) {
+            mDropTargets[i] = outList.get(i);
+            mDropTargets[i].setDropTargetBar(this);
+            dragController.addDragListener(mDropTargets[i]);
+            dragController.addDropTarget(mDropTargets[i]);
+        }
+    }
+
+    private static void findDropTargets(View view, ArrayList<ButtonDropTarget> outTargets) {
+        if (view instanceof ButtonDropTarget) {
+            outTargets.add((ButtonDropTarget) view);
+        } else if (view instanceof ViewGroup) {
+            ViewGroup vg = (ViewGroup) view;
+            for (int i = vg.getChildCount() - 1; i >= 0; i--) {
+                findDropTargets(vg.getChildAt(i), outTargets);
+            }
+        }
     }
 
     @Override
@@ -130,20 +152,6 @@
         return result;
     }
 
-    private void setupButtonDropTarget(View view, DragController dragController) {
-        if (view instanceof ButtonDropTarget) {
-            ButtonDropTarget bdt = (ButtonDropTarget) view;
-            bdt.setDropTargetBar(this);
-            dragController.addDragListener(bdt);
-            dragController.addDropTarget(bdt);
-        } else if (view instanceof ViewGroup) {
-            ViewGroup vg = (ViewGroup) view;
-            for (int i = vg.getChildCount() - 1; i >= 0; i--) {
-                setupButtonDropTarget(vg.getChildAt(i), dragController);
-            }
-        }
-    }
-
     private void animateToVisibility(boolean isVisible) {
         if (mVisible != isVisible) {
             mVisible = isVisible;
@@ -190,4 +198,8 @@
             mDeferOnDragEnd = false;
         }
     }
+
+    public ButtonDropTarget[] getDropTargets() {
+        return mDropTargets;
+    }
 }
diff --git a/src/com/android/launcher3/ExtendedEditText.java b/src/com/android/launcher3/ExtendedEditText.java
index 596aa8f..403c8b8 100644
--- a/src/com/android/launcher3/ExtendedEditText.java
+++ b/src/com/android/launcher3/ExtendedEditText.java
@@ -16,12 +16,16 @@
 package com.android.launcher3;
 
 import android.content.Context;
+import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.view.DragEvent;
 import android.view.KeyEvent;
+import android.view.View;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.EditText;
 
+import com.android.launcher3.util.UiThreadHelper;
+
 
 /**
  * The edit text that reports back when the back key has been pressed.
@@ -102,8 +106,7 @@
     }
 
     public void dispatchBackKey() {
-        ((InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE))
-                .hideSoftInputFromWindow(getWindowToken(), 0);
+        UiThreadHelper.hideKeyboardAsync(getContext(), getWindowToken());
         if (mBackKeyListener != null) {
             mBackKeyListener.onBackKey();
         }
@@ -121,4 +124,17 @@
     public boolean isSuggestionsEnabled() {
         return !mForceDisableSuggestions && super.isSuggestionsEnabled();
     }
+
+    public void reset() {
+        if (!TextUtils.isEmpty(getText())) {
+            setText("");
+        }
+        if (isFocused()) {
+            View nextFocus = focusSearch(View.FOCUS_DOWN);
+            if (nextFocus != null) {
+                nextFocus.requestFocus();
+            }
+        }
+        UiThreadHelper.hideKeyboardAsync(getContext(), getWindowToken());
+    }
 }
diff --git a/src/com/android/launcher3/FirstFrameAnimatorHelper.java b/src/com/android/launcher3/FirstFrameAnimatorHelper.java
index 3cbc989..cea7e43 100644
--- a/src/com/android/launcher3/FirstFrameAnimatorHelper.java
+++ b/src/com/android/launcher3/FirstFrameAnimatorHelper.java
@@ -24,6 +24,7 @@
 import android.view.ViewPropertyAnimator;
 import android.view.ViewTreeObserver;
 import com.android.launcher3.util.Thunk;
+import com.android.launcher3.util.TraceHelper;
 
 /*
  *  This is a helper class that listens to updates from the corresponding animation.
@@ -71,15 +72,12 @@
         if (sGlobalDrawListener != null) {
             view.getViewTreeObserver().removeOnDrawListener(sGlobalDrawListener);
         }
+
+        TraceHelper.beginSection("TICK");
         sGlobalDrawListener = new ViewTreeObserver.OnDrawListener() {
-                private long mTime = System.currentTimeMillis();
                 public void onDraw() {
                     sGlobalFrameCounter++;
-                    if (DEBUG) {
-                        long newTime = System.currentTimeMillis();
-                        Log.d(TAG, "TICK " + (newTime - mTime));
-                        mTime = newTime;
-                    }
+                    TraceHelper.partitionSection("TICK", "Frame drawn");
                 }
             };
         view.getViewTreeObserver().addOnDrawListener(sGlobalDrawListener);
diff --git a/src/com/android/launcher3/FocusHelper.java b/src/com/android/launcher3/FocusHelper.java
index fe7acda..1f18ea1 100644
--- a/src/com/android/launcher3/FocusHelper.java
+++ b/src/com/android/launcher3/FocusHelper.java
@@ -288,25 +288,11 @@
             case FocusLogic.PREVIOUS_PAGE_RIGHT_COLUMN:
                 // Go to the previous page but keep the focus on the same hotseat icon.
                 workspace.snapToPage(pageIndex - 1);
-                // If the page we are going to is fullscreen, have it take the focus from hotseat.
-                CellLayout prevPage = (CellLayout) workspace.getPageAt(pageIndex - 1);
-                boolean isPrevPageFullscreen = ((CellLayout.LayoutParams) prevPage
-                        .getShortcutsAndWidgets().getChildAt(0).getLayoutParams()).isFullscreen;
-                if (isPrevPageFullscreen) {
-                    workspace.getPageAt(pageIndex - 1).requestFocus();
-                }
                 break;
             case FocusLogic.NEXT_PAGE_LEFT_COLUMN:
             case FocusLogic.NEXT_PAGE_RIGHT_COLUMN:
                 // Go to the next page but keep the focus on the same hotseat icon.
                 workspace.snapToPage(pageIndex + 1);
-                // If the page we are going to is fullscreen, have it take the focus from hotseat.
-                CellLayout nextPage = (CellLayout) workspace.getPageAt(pageIndex + 1);
-                boolean isNextPageFullscreen = ((CellLayout.LayoutParams) nextPage
-                        .getShortcutsAndWidgets().getChildAt(0).getLayoutParams()).isFullscreen;
-                if (isNextPageFullscreen) {
-                    workspace.getPageAt(pageIndex + 1).requestFocus();
-                }
                 break;
         }
         if (parent == iconParent && newIconIndex >= iconParent.getChildCount()) {
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index a6d80e3..09f9e82 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -16,16 +16,9 @@
 
 package com.android.launcher3;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ArgbEvaluator;
-import android.animation.ValueAnimator;
 import android.content.Context;
-import android.graphics.Color;
 import android.graphics.Rect;
-import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
-import android.support.v4.graphics.ColorUtils;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
@@ -35,11 +28,9 @@
 import android.widget.TextView;
 
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.dynamicui.ExtractedColors;
 import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
-import com.android.launcher3.util.Themes;
 
 public class Hotseat extends FrameLayout
         implements UserEventDispatcher.LogContainerProvider {
@@ -51,12 +42,6 @@
     @ViewDebug.ExportedProperty(category = "launcher")
     private final boolean mHasVerticalHotseat;
 
-    @ViewDebug.ExportedProperty(category = "launcher")
-    private int mBackgroundColor;
-    @ViewDebug.ExportedProperty(category = "launcher")
-    private ColorDrawable mBackground;
-    private ValueAnimator mBackgroundColorAnimator;
-
     public Hotseat(Context context) {
         this(context, null);
     }
@@ -69,12 +54,6 @@
         super(context, attrs, defStyle);
         mLauncher = Launcher.getLauncher(context);
         mHasVerticalHotseat = mLauncher.getDeviceProfile().isVerticalBarLayout();
-        mBackgroundColor = ColorUtils.setAlphaComponent(
-                Themes.getAttrColor(context, android.R.attr.colorPrimary), 0);
-        mBackground = new ColorDrawable(mBackgroundColor);
-        if (!FeatureFlags.LAUNCHER3_GRADIENT_ALL_APPS) {
-            setBackground(mBackground);
-        }
     }
 
     public CellLayout getLayout() {
@@ -177,49 +156,4 @@
         target.gridY = info.cellY;
         targetParent.containerType = ContainerType.HOTSEAT;
     }
-
-    public void updateColor(ExtractedColors extractedColors, boolean animate) {
-        if (FeatureFlags.LAUNCHER3_GRADIENT_ALL_APPS) {
-            // not hotseat visible
-            return;
-        }
-        if (!mHasVerticalHotseat) {
-            int color = extractedColors.getColor(ExtractedColors.HOTSEAT_INDEX);
-            if (mBackgroundColorAnimator != null) {
-                mBackgroundColorAnimator.cancel();
-            }
-            if (!animate) {
-                setBackgroundColor(color);
-            } else {
-                mBackgroundColorAnimator = ValueAnimator.ofInt(mBackgroundColor, color);
-                mBackgroundColorAnimator.setEvaluator(new ArgbEvaluator());
-                mBackgroundColorAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-                    @Override
-                    public void onAnimationUpdate(ValueAnimator animation) {
-                        mBackground.setColor((Integer) animation.getAnimatedValue());
-                    }
-                });
-                mBackgroundColorAnimator.addListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        mBackgroundColorAnimator = null;
-                    }
-                });
-                mBackgroundColorAnimator.start();
-            }
-            mBackgroundColor = color;
-        }
-    }
-
-    public void setBackgroundTransparent(boolean enable) {
-        if (enable) {
-            mBackground.setAlpha(0);
-        } else {
-            mBackground.setAlpha(255);
-        }
-    }
-
-    public int getBackgroundDrawableColor() {
-        return mBackgroundColor;
-    }
 }
diff --git a/src/com/android/launcher3/InfoDropTarget.java b/src/com/android/launcher3/InfoDropTarget.java
index f919dd0..289242f 100644
--- a/src/com/android/launcher3/InfoDropTarget.java
+++ b/src/com/android/launcher3/InfoDropTarget.java
@@ -26,6 +26,7 @@
 import android.util.Log;
 import android.widget.Toast;
 
+import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.util.Themes;
 
@@ -49,28 +50,29 @@
     }
 
     @Override
-    public void completeDrop(DragObject d) {
-        DropTargetResultCallback callback = d.dragSource instanceof DropTargetResultCallback
-                ? (DropTargetResultCallback) d.dragSource : null;
-        startDetailsActivityForInfo(d.dragInfo, mLauncher, callback);
+    protected ComponentName performDropAction(ItemInfo item) {
+        return performDropAction(mLauncher, item, null, null);
     }
 
     /**
      * @return Whether the activity was started.
      */
     public static boolean startDetailsActivityForInfo(
-            ItemInfo info, Launcher launcher, DropTargetResultCallback callback) {
-        return startDetailsActivityForInfo(info, launcher, callback, null, null);
+            ItemInfo info, Launcher launcher, Rect sourceBounds, Bundle opts) {
+        return performDropAction(launcher, info, sourceBounds, opts) != null;
     }
 
-    public static boolean startDetailsActivityForInfo(ItemInfo info, Launcher launcher,
-            DropTargetResultCallback callback, Rect sourceBounds, Bundle opts) {
+    /**
+     * Performs the drop action and returns the target component for the dragObject or null if
+     * the action was not performed.
+     */
+    private static ComponentName performDropAction(Context context, ItemInfo info,
+            Rect sourceBounds, Bundle opts) {
         if (info instanceof PromiseAppInfo) {
             PromiseAppInfo promiseAppInfo = (PromiseAppInfo) info;
-            launcher.startActivity(promiseAppInfo.getMarketIntent());
-            return true;
+            context.startActivity(promiseAppInfo.getMarketIntent());
+            return null;
         }
-        boolean result = false;
         ComponentName componentName = null;
         if (info instanceof AppInfo) {
             componentName = ((AppInfo) info).componentName;
@@ -83,29 +85,27 @@
         }
         if (componentName != null) {
             try {
-                LauncherAppsCompat.getInstance(launcher)
+                LauncherAppsCompat.getInstance(context)
                         .showAppDetailsForProfile(componentName, info.user, sourceBounds, opts);
-                result = true;
+                return componentName;
             } catch (SecurityException | ActivityNotFoundException e) {
-                Toast.makeText(launcher, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
+                Toast.makeText(context, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
                 Log.e(TAG, "Unable to launch settings", e);
             }
         }
-
-        if (callback != null) {
-            sendUninstallResult(launcher, result, componentName, info.user, callback);
-        }
-        return result;
+        return null;
     }
 
     @Override
-    protected boolean supportsDrop(DragSource source, ItemInfo info) {
-        return source.supportsAppInfoDropTarget() && supportsDrop(getContext(), info);
+    public int getAccessibilityAction() {
+        return LauncherAccessibilityDelegate.INFO;
     }
 
-    public static boolean supportsDrop(Context context, ItemInfo info) {
+    @Override
+    protected boolean supportsDrop(ItemInfo info) {
         // Only show the App Info drop target if developer settings are enabled.
-        boolean developmentSettingsEnabled = Settings.Global.getInt(context.getContentResolver(),
+        boolean developmentSettingsEnabled = Settings.Global.getInt(
+                getContext().getContentResolver(),
                 Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) == 1;
         if (!developmentSettingsEnabled) {
             return false;
diff --git a/src/com/android/launcher3/InsettableFrameLayout.java b/src/com/android/launcher3/InsettableFrameLayout.java
index be76490..60f5ca2 100644
--- a/src/com/android/launcher3/InsettableFrameLayout.java
+++ b/src/com/android/launcher3/InsettableFrameLayout.java
@@ -9,8 +9,7 @@
 import android.view.ViewGroup;
 import android.widget.FrameLayout;
 
-public class InsettableFrameLayout extends FrameLayout implements
-    ViewGroup.OnHierarchyChangeListener, Insettable {
+public class InsettableFrameLayout extends FrameLayout implements Insettable {
 
     @ViewDebug.ExportedProperty(category = "launcher")
     protected Rect mInsets = new Rect();
@@ -21,7 +20,6 @@
 
     public InsettableFrameLayout(Context context, AttributeSet attrs) {
         super(context, attrs);
-        setOnHierarchyChangeListener(this);
     }
 
     public void setFrameLayoutChildInsets(View child, Rect newInsets, Rect oldInsets) {
@@ -95,12 +93,8 @@
     }
 
     @Override
-    public void onChildViewAdded(View parent, View child) {
+    public void onViewAdded(View child) {
+        super.onViewAdded(child);
         setFrameLayoutChildInsets(child, mInsets, new Rect());
     }
-
-    @Override
-    public void onChildViewRemoved(View parent, View child) {
-    }
-
 }
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index 0370777..df1eec6 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -27,7 +27,9 @@
 import android.content.pm.PackageManager;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
+import android.os.Handler;
 import android.os.Looper;
+import android.os.Message;
 import android.os.Parcelable;
 import android.os.Process;
 import android.os.UserHandle;
@@ -61,6 +63,9 @@
 
 public class InstallShortcutReceiver extends BroadcastReceiver {
 
+    private static final int MSG_ADD_TO_QUEUE = 1;
+    private static final int MSG_FLUSH_QUEUE = 2;
+
     public static final int FLAG_ACTIVITY_PAUSED = 1;
     public static final int FLAG_LOADER_RUNNING = 2;
     public static final int FLAG_DRAG_AND_DROP = 4;
@@ -93,73 +98,98 @@
     public static final int NEW_SHORTCUT_BOUNCE_DURATION = 450;
     public static final int NEW_SHORTCUT_STAGGER_DELAY = 85;
 
-    private static final Object sLock = new Object();
+    private static final Handler sHandler = new Handler(LauncherModel.getWorkerLooper()) {
 
-    private static void addToInstallQueue(
-            SharedPreferences sharedPrefs, PendingInstallShortcutInfo info) {
-        synchronized(sLock) {
-            String encoded = info.encodeToString();
-            if (encoded != null) {
-                Set<String> strings = sharedPrefs.getStringSet(APPS_PENDING_INSTALL, null);
-                strings = (strings != null) ? new HashSet<>(strings) : new HashSet<String>(1);
-                strings.add(encoded);
-                sharedPrefs.edit().putStringSet(APPS_PENDING_INSTALL, strings).apply();
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_ADD_TO_QUEUE: {
+                    Pair<Context, PendingInstallShortcutInfo> pair =
+                            (Pair<Context, PendingInstallShortcutInfo>) msg.obj;
+                    String encoded = pair.second.encodeToString();
+                    SharedPreferences prefs = Utilities.getPrefs(pair.first);
+                    Set<String> strings = prefs.getStringSet(APPS_PENDING_INSTALL, null);
+                    strings = (strings != null) ? new HashSet<>(strings) : new HashSet<String>(1);
+                    strings.add(encoded);
+                    prefs.edit().putStringSet(APPS_PENDING_INSTALL, strings).apply();
+                    return;
+                }
+                case MSG_FLUSH_QUEUE: {
+                    Context context = (Context) msg.obj;
+                    LauncherModel model = LauncherAppState.getInstance(context).getModel();
+                    if (model.getCallback() == null) {
+                        // Launcher not loaded
+                        return;
+                    }
+
+                    ArrayList<Pair<ItemInfo, Object>> installQueue = new ArrayList<>();
+                    SharedPreferences prefs = Utilities.getPrefs(context);
+                    Set<String> strings = prefs.getStringSet(APPS_PENDING_INSTALL, null);
+                    if (DBG) Log.d(TAG, "Getting and clearing APPS_PENDING_INSTALL: " + strings);
+                    if (strings == null) {
+                        return;
+                    }
+
+                    LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
+                    for (String encoded : strings) {
+                        PendingInstallShortcutInfo info = decode(encoded, context);
+                        if (info == null) {
+                            continue;
+                        }
+
+                        String pkg = getIntentPackage(info.launchIntent);
+                        if (!TextUtils.isEmpty(pkg)
+                                && !launcherApps.isPackageEnabledForProfile(pkg, info.user)) {
+                            if (DBG) Log.d(TAG, "Ignoring shortcut for absent package: "
+                                    + info.launchIntent);
+                            continue;
+                        }
+
+                        // Generate a shortcut info to add into the model
+                        installQueue.add(info.getItemInfo());
+                    }
+                    prefs.edit().remove(APPS_PENDING_INSTALL).apply();
+                    if (!installQueue.isEmpty()) {
+                        model.addAndBindAddedWorkspaceItems(installQueue);
+                    }
+                    return;
+                }
             }
         }
-    }
+    };
 
     public static void removeFromInstallQueue(Context context, HashSet<String> packageNames,
             UserHandle user) {
         if (packageNames.isEmpty()) {
             return;
         }
+        Preconditions.assertWorkerThread();
+
         SharedPreferences sp = Utilities.getPrefs(context);
-        synchronized(sLock) {
-            Set<String> strings = sp.getStringSet(APPS_PENDING_INSTALL, null);
-            if (DBG) {
-                Log.d(TAG, "APPS_PENDING_INSTALL: " + strings
-                        + ", removing packages: " + packageNames);
-            }
-            if (Utilities.isEmpty(strings)) {
-                return;
-            }
-            Set<String> newStrings = new HashSet<>(strings);
-            Iterator<String> newStringsIter = newStrings.iterator();
-            while (newStringsIter.hasNext()) {
-                String encoded = newStringsIter.next();
-                try {
-                    Decoder decoder = new Decoder(encoded, context);
-                    if (packageNames.contains(getIntentPackage(decoder.launcherIntent)) &&
-                            user.equals(decoder.user)) {
-                        newStringsIter.remove();
-                    }
-                } catch (JSONException | URISyntaxException e) {
-                    Log.d(TAG, "Exception reading shortcut to add: " + e);
+        Set<String> strings = sp.getStringSet(APPS_PENDING_INSTALL, null);
+        if (DBG) {
+            Log.d(TAG, "APPS_PENDING_INSTALL: " + strings
+                    + ", removing packages: " + packageNames);
+        }
+        if (Utilities.isEmpty(strings)) {
+            return;
+        }
+        Set<String> newStrings = new HashSet<>(strings);
+        Iterator<String> newStringsIter = newStrings.iterator();
+        while (newStringsIter.hasNext()) {
+            String encoded = newStringsIter.next();
+            try {
+                Decoder decoder = new Decoder(encoded, context);
+                if (packageNames.contains(getIntentPackage(decoder.launcherIntent)) &&
+                        user.equals(decoder.user)) {
                     newStringsIter.remove();
                 }
+            } catch (JSONException | URISyntaxException e) {
+                Log.d(TAG, "Exception reading shortcut to add: " + e);
+                newStringsIter.remove();
             }
-            sp.edit().putStringSet(APPS_PENDING_INSTALL, newStrings).apply();
         }
-    }
-
-    private static ArrayList<PendingInstallShortcutInfo> getAndClearInstallQueue(Context context) {
-        SharedPreferences sharedPrefs = Utilities.getPrefs(context);
-        synchronized(sLock) {
-            ArrayList<PendingInstallShortcutInfo> infos = new ArrayList<>();
-            Set<String> strings = sharedPrefs.getStringSet(APPS_PENDING_INSTALL, null);
-            if (DBG) Log.d(TAG, "Getting and clearing APPS_PENDING_INSTALL: " + strings);
-            if (strings == null) {
-                return infos;
-            }
-            for (String encoded : strings) {
-                PendingInstallShortcutInfo info = decode(encoded, context);
-                if (info != null) {
-                    infos.add(info);
-                }
-            }
-            sharedPrefs.edit().putStringSet(APPS_PENDING_INSTALL, new HashSet<String>()).apply();
-            return infos;
-        }
+        sp.edit().putStringSet(APPS_PENDING_INSTALL, newStrings).apply();
     }
 
     public void onReceive(Context context, Intent data) {
@@ -256,7 +286,7 @@
 
     private static void queuePendingShortcutInfo(PendingInstallShortcutInfo info, Context context) {
         // Queue the item up for adding if launcher has not loaded properly yet
-        addToInstallQueue(Utilities.getPrefs(context), info);
+        Message.obtain(sHandler, MSG_ADD_TO_QUEUE, Pair.create(context, info)).sendToTarget();
         flushInstallQueue(context);
     }
 
@@ -269,17 +299,10 @@
     }
 
     static void flushInstallQueue(Context context) {
-        LauncherModel model = LauncherAppState.getInstance(context).getModel();
-        boolean launcherNotLoaded = model.getCallback() == null;
-        if (sInstallQueueDisabledFlags != 0 || launcherNotLoaded) {
+        if (sInstallQueueDisabledFlags != 0) {
             return;
         }
-
-        ArrayList<PendingInstallShortcutInfo> items = getAndClearInstallQueue(context);
-        if (!items.isEmpty()) {
-            model.addAndBindAddedWorkspaceItems(
-                    new LazyShortcutsProvider(context.getApplicationContext(), items));
-        }
+        Message.obtain(sHandler, MSG_FLUSH_QUEUE, context.getApplicationContext()).sendToTarget();
     }
 
     /**
@@ -601,42 +624,6 @@
         return new PendingInstallShortcutInfo(info, original.mContext);
     }
 
-    private static class LazyShortcutsProvider extends Provider<List<Pair<ItemInfo, Object>>> {
-
-        private final Context mContext;
-        private final ArrayList<PendingInstallShortcutInfo> mPendingItems;
-
-        public LazyShortcutsProvider(Context context, ArrayList<PendingInstallShortcutInfo> items) {
-            mContext = context;
-            mPendingItems = items;
-        }
-
-        /**
-         * This must be called on the background thread as this requires multiple calls to
-         * packageManager and icon cache.
-         */
-        @Override
-        public ArrayList<Pair<ItemInfo, Object>> get() {
-            Preconditions.assertNonUiThread();
-            ArrayList<Pair<ItemInfo, Object>> installQueue = new ArrayList<>();
-            LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(mContext);
-            for (PendingInstallShortcutInfo pendingInfo : mPendingItems) {
-                // If the intent specifies a package, make sure the package exists
-                String packageName = getIntentPackage(pendingInfo.launchIntent);
-                if (!TextUtils.isEmpty(packageName) && !launcherApps.isPackageEnabledForProfile(
-                        packageName, pendingInfo.user)) {
-                    if (DBG) Log.d(TAG, "Ignoring shortcut for absent package: "
-                            + pendingInfo.launchIntent);
-                    continue;
-                }
-
-                // Generate a shortcut info to add into the model
-                installQueue.add(pendingInfo.getItemInfo());
-            }
-            return installQueue;
-        }
-    }
-
     private static ShortcutInfo createShortcutInfo(Intent data, LauncherAppState app) {
         Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
         String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
diff --git a/src/com/android/launcher3/ItemInfoWithIcon.java b/src/com/android/launcher3/ItemInfoWithIcon.java
index 1e020e2..fea4dda 100644
--- a/src/com/android/launcher3/ItemInfoWithIcon.java
+++ b/src/com/android/launcher3/ItemInfoWithIcon.java
@@ -33,11 +33,75 @@
      */
     public boolean usingLowResIcon;
 
+    /**
+     * Indicates that the icon is disabled due to safe mode restrictions.
+     */
+    public static final int FLAG_DISABLED_SAFEMODE = 1 << 0;
+
+    /**
+     * Indicates that the icon is disabled as the app is not available.
+     */
+    public static final int FLAG_DISABLED_NOT_AVAILABLE = 1 << 1;
+
+    /**
+     * Indicates that the icon is disabled as the app is suspended
+     */
+    public static final int FLAG_DISABLED_SUSPENDED = 1 << 2;
+
+    /**
+     * Indicates that the icon is disabled as the user is in quiet mode.
+     */
+    public static final int FLAG_DISABLED_QUIET_USER = 1 << 3;
+
+    /**
+     * Indicates that the icon is disabled as the publisher has disabled the actual shortcut.
+     */
+    public static final int FLAG_DISABLED_BY_PUBLISHER = 1 << 4;
+
+    /**
+     * Indicates that the icon is disabled as the user partition is currently locked.
+     */
+    public static final int FLAG_DISABLED_LOCKED_USER = 1 << 5;
+
+    public static final int FLAG_DISABLED_MASK = FLAG_DISABLED_SAFEMODE |
+            FLAG_DISABLED_NOT_AVAILABLE | FLAG_DISABLED_SUSPENDED |
+            FLAG_DISABLED_QUIET_USER | FLAG_DISABLED_BY_PUBLISHER | FLAG_DISABLED_LOCKED_USER;
+
+    /**
+     * The item points to a system app.
+     */
+    public static final int FLAG_SYSTEM_YES = 1 << 6;
+
+    /**
+     * The item points to a non system app.
+     */
+    public static final int FLAG_SYSTEM_NO = 1 << 7;
+
+    public static final int FLAG_SYSTEM_MASK = FLAG_SYSTEM_YES | FLAG_SYSTEM_NO;
+
+    /**
+     * Flag indicating that the icon is an {@link android.graphics.drawable.AdaptiveIconDrawable}
+     * that can be optimized in various way.
+     */
+    public static final int FLAG_ADAPTIVE_ICON = 1 << 8;
+
+    /**
+     * Status associated with the system state of the underlying item. This is calculated every
+     * time a new info is created and not persisted on the disk.
+     */
+    public int runtimeStatusFlags = 0;
+
     protected ItemInfoWithIcon() { }
 
     protected ItemInfoWithIcon(ItemInfoWithIcon info) {
         super(info);
         iconBitmap = info.iconBitmap;
         usingLowResIcon = info.usingLowResIcon;
+        runtimeStatusFlags = info.runtimeStatusFlags;
+    }
+
+    @Override
+    public boolean isDisabled() {
+        return (runtimeStatusFlags & FLAG_DISABLED_MASK) != 0;
     }
 }
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 32af059..b7986da 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -16,6 +16,22 @@
 
 package com.android.launcher3;
 
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_BY_PUBLISHER;
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER;
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_QUIET_USER;
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SAFEMODE;
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SUSPENDED;
+import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
+import static com.android.launcher3.LauncherState.ALL_APPS;
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
+import static com.android.launcher3.util.RunnableWithId.RUNNABLE_ID_BIND_APPS;
+import static com.android.launcher3.util.RunnableWithId.RUNNABLE_ID_BIND_WIDGETS;
+
 import android.Manifest;
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -41,20 +57,18 @@
 import android.content.IntentSender;
 import android.content.SharedPreferences;
 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
-import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
 import android.database.sqlite.SQLiteDatabase;
 import android.graphics.Point;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.os.AsyncTask;
 import android.os.Build;
 import android.os.Bundle;
-import android.os.Handler;
+import android.os.Parcelable;
 import android.os.Process;
 import android.os.StrictMode;
-import android.os.SystemClock;
-import android.os.Trace;
 import android.os.UserHandle;
 import android.support.annotation.Nullable;
 import android.text.Selection;
@@ -62,6 +76,7 @@
 import android.text.TextUtils;
 import android.text.method.TextKeyListener;
 import android.util.Log;
+import android.util.SparseArray;
 import android.view.Display;
 import android.view.HapticFeedbackConstants;
 import android.view.KeyEvent;
@@ -73,10 +88,7 @@
 import android.view.View;
 import android.view.View.OnLongClickListener;
 import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
-import android.view.WindowManager;
 import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
 import android.view.animation.OvershootInterpolator;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.Toast;
@@ -87,7 +99,6 @@
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
 import com.android.launcher3.allapps.AllAppsContainerView;
 import com.android.launcher3.allapps.AllAppsTransitionController;
-import com.android.launcher3.anim.AnimationLayerSet;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.LauncherAppsCompatVO;
@@ -97,8 +108,6 @@
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.dragndrop.DragView;
-import com.android.launcher3.dragndrop.PinItemDragListener;
-import com.android.launcher3.dynamicui.ExtractedColors;
 import com.android.launcher3.dynamicui.WallpaperColorInfo;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIcon;
@@ -107,65 +116,61 @@
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.model.ModelWriter;
-import com.android.launcher3.model.PackageItemInfo;
-import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.notification.NotificationListener;
 import com.android.launcher3.pageindicators.PageIndicator;
 import com.android.launcher3.popup.PopupContainerWithArrow;
 import com.android.launcher3.popup.PopupDataProvider;
 import com.android.launcher3.shortcuts.DeepShortcutManager;
+import com.android.launcher3.states.AllAppsState;
+import com.android.launcher3.states.InternalStateHandler;
+import com.android.launcher3.uioverrides.UiFactory;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.util.ActivityResultInfo;
-import com.android.launcher3.util.RunnableWithId;
 import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.ComponentKeyMapper;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.MultiHashMap;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.PendingRequestArgs;
+import com.android.launcher3.util.RunnableWithId;
 import com.android.launcher3.util.SystemUiController;
-import com.android.launcher3.util.TestingUtils;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.Thunk;
+import com.android.launcher3.util.TraceHelper;
+import com.android.launcher3.util.UiThreadHelper;
 import com.android.launcher3.util.ViewOnDrawExecutor;
 import com.android.launcher3.widget.PendingAddShortcutInfo;
 import com.android.launcher3.widget.PendingAddWidgetInfo;
 import com.android.launcher3.widget.WidgetAddFlowHandler;
 import com.android.launcher3.widget.WidgetHostViewLoader;
-import com.android.launcher3.widget.WidgetsContainerView;
-
+import com.android.launcher3.widget.WidgetListRowEntry;
+import com.android.launcher3.widget.WidgetsFullSheet;
+import com.android.launcher3.widget.custom.CustomWidgetParser;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.Executor;
 
-import static com.android.launcher3.util.RunnableWithId.RUNNABLE_ID_BIND_APPS;
-import static com.android.launcher3.util.RunnableWithId.RUNNABLE_ID_BIND_WIDGETS;
-
 /**
  * Default launcher application.
  */
 public class Launcher extends BaseActivity
         implements LauncherExterns, View.OnClickListener, OnLongClickListener,
                    LauncherModel.Callbacks, View.OnTouchListener, LauncherProviderChangeListener,
-                   AccessibilityManager.AccessibilityStateChangeListener,
                    WallpaperColorInfo.OnThemeChangeListener {
     public static final String TAG = "Launcher";
     static final boolean LOGD = false;
 
-    static final boolean DEBUG_WIDGETS = false;
     static final boolean DEBUG_STRICT_MODE = false;
-    static final boolean DEBUG_RESUME_TIME = false;
 
     private static final int REQUEST_CREATE_SHORTCUT = 1;
     private static final int REQUEST_CREATE_APPWIDGET = 5;
@@ -187,11 +192,6 @@
      */
     protected static final int REQUEST_LAST = 100;
 
-    private static final int SOFT_INPUT_MODE_DEFAULT =
-            WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN;
-    private static final int SOFT_INPUT_MODE_ALL_APPS =
-            WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
-
     // The Intent extra that defines whether to ignore the launch animation
     static final String INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION =
             "com.android.launcher3.intent.extra.shortcut.INGORE_LAUNCH_ANIMATION";
@@ -204,19 +204,13 @@
     private static final String RUNTIME_STATE_PENDING_REQUEST_ARGS = "launcher.request_args";
     // Type: ActivityResultInfo
     private static final String RUNTIME_STATE_PENDING_ACTIVITY_RESULT = "launcher.activity_result";
+    // Type: SparseArray<Parcelable>
+    private static final String RUNTIME_STATE_WIDGET_PANEL = "launcher.widget_panel";
 
-    static final String APPS_VIEW_SHOWN = "launcher.apps_view_shown";
-
-    /** The different states that Launcher can be in. */
-    enum State { NONE, WORKSPACE, WORKSPACE_SPRING_LOADED, APPS, APPS_SPRING_LOADED,
-        WIDGETS, WIDGETS_SPRING_LOADED }
-
-    @Thunk State mState = State.WORKSPACE;
-    @Thunk LauncherStateTransitionAnimation mStateTransitionAnimation;
+    private LauncherStateManager mStateManager;
 
     private boolean mIsSafeModeEnabled;
 
-    public static final int EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT = 500;
     private static final int ON_ACTIVITY_RESULT_ANIMATION_DELAY = 500;
 
     // How long to wait before the new-shortcut animation automatically pans the workspace
@@ -224,25 +218,19 @@
     private static final int NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS = 5;
     @Thunk static final int NEW_APPS_ANIMATION_DELAY = 500;
 
-    private final ExtractedColors mExtractedColors = new ExtractedColors();
-
     @Thunk Workspace mWorkspace;
     private View mLauncherView;
     @Thunk DragLayer mDragLayer;
     private DragController mDragController;
 
-    public View mWeightWatcher;
-
     private AppWidgetManagerCompat mAppWidgetManager;
     private LauncherAppWidgetHost mAppWidgetHost;
 
     private final int[] mTmpAddItemCellCoordinates = new int[2];
 
     @Thunk Hotseat mHotseat;
-    private ViewGroup mOverviewPanel;
 
     private View mAllAppsButton;
-    private View mWidgetsButton;
 
     private DropTargetBar mDropTargetBar;
 
@@ -250,18 +238,13 @@
     @Thunk AllAppsContainerView mAppsView;
     AllAppsTransitionController mAllAppsController;
 
-    // Main container view and the model for the widget tray screen.
-    @Thunk WidgetsContainerView mWidgetsView;
+    // UI and state for the overview panel
+    private ViewGroup mOverviewPanel;
 
     // We need to store the orientation Launcher was created with, due to a bug (b/64916689)
     // that results in widgets being inflated in the wrong orientation.
     private int mOrientation;
 
-    // We set the state in both onCreate and then onNewIntent in some cases, which causes both
-    // scroll issues (because the workspace may not have been measured yet) and extra work.
-    // Instead, just save the state that we need to restore Launcher to, and commit it in onResume.
-    private State mOnResumeState = State.NONE;
-
     private SpannableStringBuilder mDefaultKeySsb = null;
 
     @Thunk boolean mWorkspaceLoading = true;
@@ -270,14 +253,14 @@
     private boolean mOnResumeNeedsLoad;
 
     private final ArrayList<Runnable> mBindOnResumeCallbacks = new ArrayList<>();
-    private final ArrayList<Runnable> mOnResumeCallbacks = new ArrayList<>();
+    private OnResumeCallback mOnResumeCallback;
+
     private ViewOnDrawExecutor mPendingExecutor;
 
     private LauncherModel mModel;
     private ModelWriter mModelWriter;
     private IconCache mIconCache;
     private LauncherAccessibilityDelegate mAccessibilityDelegate;
-    private final Handler mHandler = new Handler();
     private boolean mHasFocus = false;
 
     private ObjectAnimator mScrimAnimator;
@@ -285,45 +268,12 @@
 
     private PopupDataProvider mPopupDataProvider;
 
-    // Determines how long to wait after a rotation before restoring the screen orientation to
-    // match the sensor state.
-    private static final int RESTORE_SCREEN_ORIENTATION_DELAY = 500;
-
     private final ArrayList<Integer> mSynchronouslyBoundPages = new ArrayList<>();
 
     // We only want to get the SharedPreferences once since it does an FS stat each time we get
     // it from the context.
     private SharedPreferences mSharedPrefs;
 
-    private boolean mMoveToDefaultScreenFromNewIntent;
-
-    // This is set to the view that launched the activity that navigated the user away from
-    // launcher. Since there is no callback for when the activity has finished launching, enable
-    // the press state and keep this reference to reset the press state when we return to launcher.
-    private BubbleTextView mWaitingForResume;
-
-    protected static final HashMap<String, CustomAppWidget> sCustomAppWidgets =
-        new HashMap<>();
-
-    static {
-        if (TestingUtils.ENABLE_CUSTOM_WIDGET_TEST) {
-            TestingUtils.addDummyWidget(sCustomAppWidgets);
-        }
-    }
-
-    // Exiting spring loaded mode happens with a delay. This runnable object triggers the
-    // state transition. If another state transition happened during this delay,
-    // simply unregister this runnable.
-    private Runnable mExitSpringLoadedModeRunnable;
-
-    @Thunk final Runnable mBuildLayersRunnable = new Runnable() {
-        public void run() {
-            if (mWorkspace != null) {
-                mWorkspace.buildPageHardwareLayers();
-            }
-        }
-    };
-
     // Activity result which needs to be processed after workspace has loaded.
     private ActivityResultInfo mPendingActivityResult;
     /**
@@ -336,15 +286,7 @@
 
     public ViewGroupFocusHelper mFocusHandler;
     private boolean mRotationEnabled = false;
-
-    @Thunk void setOrientation() {
-        if (mRotationEnabled) {
-            unlockScreenOrientation(true);
-        } else {
-            setRequestedOrientation(
-                    ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);
-        }
-    }
+    private boolean mAppLaunchSuccess;
 
     private RotationPrefChangeHandler mRotationPrefChangeHandler;
 
@@ -364,9 +306,7 @@
                     .penaltyDeath()
                     .build());
         }
-        if (LauncherAppState.PROFILE_STARTUP) {
-            Trace.beginSection("Launcher-onCreate");
-        }
+        TraceHelper.beginSection("Launcher-onCreate");
 
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.preOnCreate();
@@ -377,6 +317,7 @@
         overrideTheme(wallpaperColorInfo.isDark(), wallpaperColorInfo.supportsDarkText());
 
         super.onCreate(savedInstanceState);
+        TraceHelper.partitionSection("Launcher-onCreate", "super call");
 
         LauncherAppState app = LauncherAppState.getInstance(this);
 
@@ -399,14 +340,11 @@
 
         mDragController = new DragController(this);
         mAllAppsController = new AllAppsTransitionController(this);
-        mStateTransitionAnimation = new LauncherStateTransitionAnimation(this, mAllAppsController);
+        mStateManager = new LauncherStateManager(this);
 
         mAppWidgetManager = AppWidgetManagerCompat.getInstance(this);
 
         mAppWidgetHost = new LauncherAppWidgetHost(this);
-        if (Utilities.ATLEAST_MARSHMALLOW) {
-            mAppWidgetHost.addProviderChangeListener(this);
-        }
         mAppWidgetHost.startListening();
 
         // If we are getting an onCreate, we can actually preempt onResume and unset mPaused here,
@@ -418,31 +356,44 @@
 
         setupViews();
         mDeviceProfile.layout(this, false /* notifyListeners */);
-        loadExtractedColorsAndColorItems();
 
         mPopupDataProvider = new PopupDataProvider(this);
 
-        ((AccessibilityManager) getSystemService(ACCESSIBILITY_SERVICE))
-                .addAccessibilityStateChangeListener(this);
-
-        lockAllApps();
-
-        restoreState(savedInstanceState);
-
-        if (LauncherAppState.PROFILE_STARTUP) {
-            Trace.endSection();
+        mRotationEnabled = getResources().getBoolean(R.bool.allow_rotation);
+        // In case we are on a device with locked rotation, we should look at preferences to check
+        // if the user has specifically allowed rotation.
+        if (!mRotationEnabled) {
+            mRotationEnabled = Utilities.isAllowRotationPrefEnabled(getApplicationContext());
+            mRotationPrefChangeHandler = new RotationPrefChangeHandler();
+            mSharedPrefs.registerOnSharedPreferenceChangeListener(mRotationPrefChangeHandler);
         }
 
+        boolean internalStateHandled = InternalStateHandler.handleCreate(this, getIntent());
+        if (internalStateHandled) {
+            // Temporarily enable the rotation
+            mRotationEnabled = true;
+
+            if (savedInstanceState != null) {
+                // InternalStateHandler has already set the appropriate state.
+                // We dont need to do anything.
+                savedInstanceState.remove(RUNTIME_STATE);
+            }
+        }
+        restoreState(savedInstanceState);
+
         // We only load the page synchronously if the user rotates (or triggers a
         // configuration change) while launcher is in the foreground
         int currentScreen = PagedView.INVALID_RESTORE_PAGE;
         if (savedInstanceState != null) {
             currentScreen = savedInstanceState.getInt(RUNTIME_STATE_CURRENT_SCREEN, currentScreen);
         }
+
         if (!mModel.startLoader(currentScreen)) {
-            // If we are not binding synchronously, show a fade in animation when
-            // the first page bind completes.
-            mDragLayer.setAlpha(0);
+            if (!internalStateHandled) {
+                // If we are not binding synchronously, show a fade in animation when
+                // the first page bind completes.
+                mDragLayer.setAlpha(0);
+            }
         } else {
             // Pages bound synchronously.
             mWorkspace.setCurrentPage(currentScreen);
@@ -454,23 +405,10 @@
         mDefaultKeySsb = new SpannableStringBuilder();
         Selection.setSelection(mDefaultKeySsb, 0);
 
-        mRotationEnabled = getResources().getBoolean(R.bool.allow_rotation);
-        // In case we are on a device with locked rotation, we should look at preferences to check
-        // if the user has specifically allowed rotation.
-        if (!mRotationEnabled) {
-            mRotationEnabled = Utilities.isAllowRotationPrefEnabled(getApplicationContext());
-            mRotationPrefChangeHandler = new RotationPrefChangeHandler();
-            mSharedPrefs.registerOnSharedPreferenceChangeListener(mRotationPrefChangeHandler);
-        }
-
-        if (PinItemDragListener.handleDragRequest(this, getIntent())) {
-            // Temporarily enable the rotation
-            mRotationEnabled = true;
-        }
-
         // On large interfaces, or on devices that a user has specifically enabled screen rotation,
         // we want the screen to auto-rotate based on the current orientation
-        setOrientation();
+        setRequestedOrientation(mRotationEnabled
+                ? SCREEN_ORIENTATION_UNSPECIFIED : SCREEN_ORIENTATION_NOSENSOR);
 
         setContentView(mLauncherView);
 
@@ -487,6 +425,8 @@
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.onCreate(savedInstanceState);
         }
+
+        TraceHelper.endSection("Launcher-onCreate");
     }
 
     @Override
@@ -494,6 +434,10 @@
         recreate();
     }
 
+    public LauncherStateManager getStateManager() {
+        return mStateManager;
+    }
+
     protected void overrideTheme(boolean isDark, boolean supportsDarkText) {
         if (isDark) {
             setTheme(R.style.LauncherThemeDark);
@@ -508,40 +452,14 @@
     }
 
     @Override
-    public void onExtractedColorsChanged() {
-        loadExtractedColorsAndColorItems();
-        mExtractedColors.notifyChange();
-    }
-
-    public ExtractedColors getExtractedColors() {
-        return mExtractedColors;
-    }
-
-    @Override
     public void onAppWidgetHostReset() {
         if (mAppWidgetHost != null) {
             mAppWidgetHost.startListening();
         }
     }
 
-    private void loadExtractedColorsAndColorItems() {
-        // TODO: do this in pre-N as well, once the extraction part is complete.
-        if (Utilities.ATLEAST_NOUGAT) {
-            mExtractedColors.load(this);
-            mHotseat.updateColor(mExtractedColors, !mPaused);
-            mWorkspace.getPageIndicator().updateColor(mExtractedColors);
-        }
-    }
-
     private LauncherCallbacks mLauncherCallbacks;
 
-    public void onPostCreate(Bundle savedInstanceState) {
-        super.onPostCreate(savedInstanceState);
-        if (mLauncherCallbacks != null) {
-            mLauncherCallbacks.onPostCreate(savedInstanceState);
-        }
-    }
-
     public void onInsetsChanged(Rect insets) {
         mDeviceProfile.updateInsets(insets);
         mDeviceProfile.layout(this, true /* notifyListeners */);
@@ -569,44 +487,6 @@
         }
     }
 
-    /** To be overridden by subclasses to hint to Launcher that we have custom content */
-    protected boolean hasCustomContentToLeft() {
-        if (mLauncherCallbacks != null) {
-            return mLauncherCallbacks.hasCustomContentToLeft();
-        }
-        return false;
-    }
-
-    /**
-     * To be overridden by subclasses to populate the custom content container and call
-     * {@link #addToCustomContentPage}. This will only be invoked if
-     * {@link #hasCustomContentToLeft()} is {@code true}.
-     */
-    protected void populateCustomContentContainer() {
-        if (mLauncherCallbacks != null) {
-            mLauncherCallbacks.populateCustomContentContainer();
-        }
-    }
-
-    /**
-     * Invoked by subclasses to signal a change to the {@link #addToCustomContentPage} value to
-     * ensure the custom content page is added or removed if necessary.
-     */
-    protected void invalidateHasCustomContentToLeft() {
-        if (mWorkspace == null || mWorkspace.getScreenOrder().isEmpty()) {
-            // Not bound yet, wait for bindScreens to be called.
-            return;
-        }
-
-        if (!mWorkspace.hasCustomContent() && hasCustomContentToLeft()) {
-            // Create the custom content page and call the subclass to populate it.
-            mWorkspace.createCustomContentContainer();
-            populateCustomContentContainer();
-        } else if (mWorkspace.hasCustomContent() && !hasCustomContentToLeft()) {
-            mWorkspace.removeCustomContentPage();
-        }
-    }
-
     public boolean isDraggingEnabled() {
         // We prevent dragging when we are loading the workspace as it is possible to pick up a view
         // that is subsequently removed from the workspace in startBinding().
@@ -689,8 +569,7 @@
         Runnable exitSpringLoaded = new Runnable() {
             @Override
             public void run() {
-                exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED),
-                        EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
+                mStateManager.goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
             }
         };
 
@@ -710,11 +589,11 @@
             }
             return;
         } else if (requestCode == REQUEST_PICK_WALLPAPER) {
-            if (resultCode == RESULT_OK && mWorkspace.isInOverviewMode()) {
+            if (resultCode == RESULT_OK && isInState(OVERVIEW)) {
                 // User could have free-scrolled between pages before picking a wallpaper; make sure
                 // we move to the closest one now.
                 mWorkspace.setCurrentPage(mWorkspace.getPageNearestToCenterOfScreen());
-                showWorkspace(false);
+                mStateManager.goToState(NORMAL, false);
             }
             return;
         }
@@ -742,7 +621,7 @@
                 final Runnable onComplete = new Runnable() {
                     @Override
                     public void run() {
-                        exitSpringLoadedDragModeDelayed(false, 0, null);
+                        getStateManager().goToState(NORMAL);
                     }
                 };
 
@@ -806,7 +685,7 @@
         }
     }
 
-    /** @Override for MNC */
+    @Override
     public void onRequestPermissionsResult(int requestCode, String[] permissions,
             int[] grantResults) {
         PendingRequestArgs pendingArgs = mPendingRequestArgs;
@@ -870,8 +749,7 @@
                 @Override
                 public void run() {
                     completeAddAppWidget(appWidgetId, requestArgs, layout, null);
-                    exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED),
-                            EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
+                    mStateManager.goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
                 }
             };
         } else if (resultCode == RESULT_CANCELED) {
@@ -896,11 +774,12 @@
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.onStop();
         }
+        mAppWidgetHost.setListenIfResumed(false);
 
-        if (Utilities.ATLEAST_NOUGAT_MR1) {
-            mAppWidgetHost.stopListening();
+        if (!mAppLaunchSuccess) {
+            getUserEventDispatcher().logActionCommand(Action.Command.STOP,
+                    mStateManager.getState().containerType);
         }
-
         NotificationListener.removeNotificationsChangedListener();
     }
 
@@ -912,10 +791,7 @@
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.onStart();
         }
-
-        if (Utilities.ATLEAST_NOUGAT_MR1) {
-            mAppWidgetHost.startListening();
-        }
+        mAppWidgetHost.setListenIfResumed(true);
 
         if (!isWorkspaceLoading()) {
             NotificationListener.setNotificationsChangedListener(mPopupDataProvider);
@@ -943,36 +819,12 @@
 
     @Override
     protected void onResume() {
-        long startTime = 0;
-        if (DEBUG_RESUME_TIME) {
-            startTime = System.currentTimeMillis();
-            Log.v(TAG, "Launcher.onResume()");
-        }
-
-        if (mLauncherCallbacks != null) {
-            mLauncherCallbacks.preOnResume();
-        }
-
+        TraceHelper.beginSection("ON_RESUME");
         super.onResume();
+        TraceHelper.partitionSection("ON_RESUME", "superCall");
+
+        mAppLaunchSuccess = false;
         getUserEventDispatcher().resetElapsedSessionMillis();
-
-        // Restore the previous launcher state
-        if (mOnResumeState == State.WORKSPACE) {
-            showWorkspace(false);
-        } else if (mOnResumeState == State.APPS) {
-            boolean launchedFromApp = (mWaitingForResume != null);
-            // Don't update the predicted apps if the user is returning to launcher in the apps
-            // view after launching an app, as they may be depending on the UI to be static to
-            // switch to another app, otherwise, if it was
-            showAppsView(false /* animated */, !launchedFromApp /* updatePredictedApps */);
-        } else if (mOnResumeState == State.WIDGETS) {
-            showWidgetsView(false, false);
-        }
-        if (mOnResumeState != State.APPS) {
-            tryAndUpdatePredictedApps();
-        }
-        mOnResumeState = State.NONE;
-
         mPaused = false;
         if (mOnResumeNeedsLoad) {
             setWorkspaceLoading(true);
@@ -982,64 +834,13 @@
         if (mBindOnResumeCallbacks.size() > 0) {
             // We might have postponed some bind calls until onResume (see waitUntilResume) --
             // execute them here
-            long startTimeCallbacks = 0;
-            if (DEBUG_RESUME_TIME) {
-                startTimeCallbacks = System.currentTimeMillis();
-            }
-
             for (int i = 0; i < mBindOnResumeCallbacks.size(); i++) {
                 mBindOnResumeCallbacks.get(i).run();
             }
             mBindOnResumeCallbacks.clear();
-            if (DEBUG_RESUME_TIME) {
-                Log.d(TAG, "Time spent processing callbacks in onResume: " +
-                    (System.currentTimeMillis() - startTimeCallbacks));
-            }
-        }
-        if (mOnResumeCallbacks.size() > 0) {
-            for (int i = 0; i < mOnResumeCallbacks.size(); i++) {
-                mOnResumeCallbacks.get(i).run();
-            }
-            mOnResumeCallbacks.clear();
         }
 
-        // Reset the pressed state of icons that were locked in the press state while activities
-        // were launching
-        if (mWaitingForResume != null) {
-            // Resets the previous workspace icon press state
-            mWaitingForResume.setStayPressed(false);
-        }
-
-        // It is possible that widgets can receive updates while launcher is not in the foreground.
-        // Consequently, the widgets will be inflated in the orientation of the foreground activity
-        // (framework issue). On resuming, we ensure that any widgets are inflated for the current
-        // orientation.
-        if (!isWorkspaceLoading()) {
-            getWorkspace().reinflateWidgetsIfNecessary();
-        }
-
-        if (DEBUG_RESUME_TIME) {
-            Log.d(TAG, "Time spent in onResume: " + (System.currentTimeMillis() - startTime));
-        }
-
-        // We want to suppress callbacks about CustomContent being shown if we have just received
-        // onNewIntent while the user was present within launcher. In that case, we post a call
-        // to move the user to the main screen (which will occur after onResume). We don't want to
-        // have onHide (from onPause), then onShow, then onHide again, which we get if we don't
-        // suppress here.
-        if (mWorkspace.getCustomContentCallbacks() != null
-                && !mMoveToDefaultScreenFromNewIntent) {
-            // If we are resuming and the custom content is the current page, we call onShow().
-            // It is also possible that onShow will instead be called slightly after first layout
-            // if PagedView#setRestorePage was set to the custom content page in onCreate().
-            if (mWorkspace.isOnOrMovingToCustomContent()) {
-                mWorkspace.getCustomContentCallbacks().onShow(true);
-            }
-        }
-        mMoveToDefaultScreenFromNewIntent = false;
-        updateInteraction(Workspace.State.NORMAL, mWorkspace.getState());
-        mWorkspace.onResume();
-
+        setOnResumeCallback(null);
         // Process any items that were added while Launcher was away.
         InstallShortcutReceiver.disableAndFlushInstallQueue(
                 InstallShortcutReceiver.FLAG_ACTIVITY_PAUSED, this);
@@ -1054,6 +855,9 @@
             mLauncherCallbacks.onResume();
         }
 
+        clearTypedText();
+
+        TraceHelper.endSection("ON_RESUME");
     }
 
     @Override
@@ -1066,32 +870,11 @@
         mDragController.cancelDrag();
         mDragController.resetLastGestureUpTime();
 
-        // We call onHide() aggressively. The custom content callbacks should be able to
-        // debounce excess onHide calls.
-        if (mWorkspace.getCustomContentCallbacks() != null) {
-            mWorkspace.getCustomContentCallbacks().onHide();
-        }
-
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.onPause();
         }
     }
 
-    public interface CustomContentCallbacks {
-        // Custom content is completely shown. {@code fromResume} indicates whether this was caused
-        // by a onResume or by scrolling otherwise.
-        void onShow(boolean fromResume);
-
-        // Custom content is completely hidden
-        void onHide();
-
-        // Custom content scroll progress changed. From 0 (not showing) to 1 (fully showing).
-        void onScrollProgressChanged(float progress);
-
-        // Indicates whether the user is allowed to scroll away from the custom content.
-        boolean isScrollingAllowed();
-    }
-
     public interface LauncherOverlay {
 
         /**
@@ -1130,7 +913,7 @@
         }
     }
 
-    protected boolean hasSettings() {
+    public boolean hasSettings() {
         if (mLauncherCallbacks != null) {
             return mLauncherCallbacks.hasSettings();
         } else {
@@ -1141,16 +924,6 @@
         }
     }
 
-    public void addToCustomContentPage(View customContent,
-            CustomContentCallbacks callbacks, String description) {
-        mWorkspace.addToCustomContentPage(customContent, callbacks, description);
-    }
-
-    // The custom content needs to offset its content to account for the QSB
-    public int getTopOffsetForCustomContent() {
-        return mWorkspace.getPaddingTop();
-    }
-
     @Override
     public Object onRetainNonConfigurationInstance() {
         // Flag the loader to stop early before switching
@@ -1206,29 +979,6 @@
         return handled;
     }
 
-    @Override
-    public boolean onKeyUp(int keyCode, KeyEvent event) {
-        if (keyCode == KeyEvent.KEYCODE_MENU) {
-            // Ignore the menu key if we are currently dragging or are on the custom content screen
-            if (!isOnCustomContent() && !mDragController.isDragging()) {
-                // Close any open floating view
-                AbstractFloatingView.closeAllOpenViews(this);
-
-                // Stop resizing any widgets
-                mWorkspace.exitWidgetResizeMode();
-
-                // Show the overview mode if we are on the workspace
-                if (mState == State.WORKSPACE && !mWorkspace.isInOverviewMode() &&
-                        !mWorkspace.isSwitchingState()) {
-                    mOverviewPanel.requestFocus();
-                    showOverviewMode(true, true /* requestButtonFocus */);
-                }
-            }
-            return true;
-        }
-        return super.onKeyUp(keyCode, event);
-    }
-
     private String getTypedText() {
         return mDefaultKeySsb.toString();
     }
@@ -1240,6 +990,10 @@
         Selection.setSelection(mDefaultKeySsb, 0);
     }
 
+    public boolean isInState(LauncherState state) {
+        return mStateManager.getState() == state;
+    }
+
     /**
      * Restores the previous state, if it exists.
      *
@@ -1250,12 +1004,11 @@
             return;
         }
 
-        int stateOrdinal = savedState.getInt(RUNTIME_STATE, State.WORKSPACE.ordinal());
-        State[] stateValues = State.values();
-        State state = (stateOrdinal >= 0 && stateOrdinal < stateValues.length)
-                ? stateValues[stateOrdinal] : State.WORKSPACE;
-        if (state == State.APPS || state == State.WIDGETS) {
-            mOnResumeState = state;
+        int stateOrdinal = savedState.getInt(RUNTIME_STATE, NORMAL.ordinal);
+        LauncherState[] stateValues = LauncherState.values();
+        LauncherState state = stateValues[stateOrdinal];
+        if (!state.doNotRestore) {
+            mStateManager.goToState(state, false /* animated */);
         }
 
         PendingRequestArgs requestArgs = savedState.getParcelable(RUNTIME_STATE_PENDING_REQUEST_ARGS);
@@ -1264,6 +1017,12 @@
         }
 
         mPendingActivityResult = savedState.getParcelable(RUNTIME_STATE_PENDING_ACTIVITY_RESULT);
+
+        SparseArray<Parcelable> widgetsState =
+                savedState.getSparseParcelableArray(RUNTIME_STATE_WIDGET_PANEL);
+        if (widgetsState != null) {
+            WidgetsFullSheet.show(this, false).restoreHierarchyState(widgetsState);
+        }
     }
 
     /**
@@ -1274,6 +1033,7 @@
         mFocusHandler = mDragLayer.getFocusIndicatorHelper();
         mWorkspace = mDragLayer.findViewById(R.id.workspace);
         mWorkspace.initParentViews(mDragLayer);
+        mOverviewPanel = findViewById(R.id.overview_panel);
 
         mLauncherView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
@@ -1288,9 +1048,6 @@
             mHotseat.setOnLongClickListener(this);
         }
 
-        // Setup the overview panel
-        setupOverviewPanel();
-
         // Setup the workspace
         mWorkspace.setHapticFeedbackEnabled(false);
         mWorkspace.setOnLongClickListener(this);
@@ -1304,58 +1061,14 @@
         // Get the search/delete/uninstall bar
         mDropTargetBar = mDragLayer.findViewById(R.id.drop_target_bar);
 
-        // Setup Apps and Widgets
-        mAppsView = (AllAppsContainerView) findViewById(R.id.apps_view);
-        mWidgetsView = (WidgetsContainerView) findViewById(R.id.widgets_view);
+        // Setup Apps
+        mAppsView = findViewById(R.id.apps_view);
 
         // Setup the drag controller (drop targets have to be added in reverse order in priority)
         mDragController.setMoveTarget(mWorkspace);
-        mDragController.addDropTarget(mWorkspace);
         mDropTargetBar.setup(mDragController);
 
         mAllAppsController.setupViews(mAppsView, mHotseat, mWorkspace);
-
-        if (TestingUtils.MEMORY_DUMP_ENABLED) {
-            TestingUtils.addWeightWatcher(this);
-        }
-    }
-
-    private void setupOverviewPanel() {
-        mOverviewPanel = (ViewGroup) findViewById(R.id.overview_panel);
-
-        // Bind wallpaper button actions
-        View wallpaperButton = findViewById(R.id.wallpaper_button);
-        new OverviewButtonClickListener(ControlType.WALLPAPER_BUTTON) {
-            @Override
-            public void handleViewClick(View view) {
-                onClickWallpaperPicker(view);
-            }
-        }.attachTo(wallpaperButton);
-
-        // Bind widget button actions
-        mWidgetsButton = findViewById(R.id.widget_button);
-        new OverviewButtonClickListener(ControlType.WIDGETS_BUTTON) {
-            @Override
-            public void handleViewClick(View view) {
-                onClickAddWidgetButton(view);
-            }
-        }.attachTo(mWidgetsButton);
-
-        // Bind settings actions
-        View settingsButton = findViewById(R.id.settings_button);
-        boolean hasSettings = hasSettings();
-        if (hasSettings) {
-            new OverviewButtonClickListener(ControlType.SETTINGS_BUTTON) {
-                @Override
-                public void handleViewClick(View view) {
-                    onClickSettingsButton(view);
-                }
-            }.attachTo(settingsButton);
-        } else {
-            settingsButton.setVisibility(View.GONE);
-        }
-
-        mOverviewPanel.setAlpha(0f);
     }
 
     /**
@@ -1366,14 +1079,6 @@
         mAllAppsButton = allAppsButton;
     }
 
-    public View getStartViewForAllAppsRevealAnimation() {
-        return FeatureFlags.NO_ALL_APPS_ICON ? mWorkspace.getPageIndicator() : mAllAppsButton;
-    }
-
-    public View getWidgetsButton() {
-        return mWidgetsButton;
-    }
-
     /**
      * Creates a view representing a shortcut.
      *
@@ -1449,7 +1154,7 @@
 
                 // If appropriate, either create a folder or add to an existing folder
                 if (mWorkspace.createUserFolderIfNecessary(view, container, layout, cellXY, 0,
-                        true, null, null)) {
+                        true, null)) {
                     return;
                 }
                 DragObject dragObject = new DragObject();
@@ -1502,17 +1207,13 @@
             appWidgetInfo = mAppWidgetManager.getLauncherAppWidgetInfo(appWidgetId);
         }
 
-        if (appWidgetInfo.isCustomWidget) {
-            appWidgetId = LauncherAppWidgetInfo.CUSTOM_WIDGET_ID;
-        }
-
         LauncherAppWidgetInfo launcherInfo;
         launcherInfo = new LauncherAppWidgetInfo(appWidgetId, appWidgetInfo.provider);
         launcherInfo.spanX = itemInfo.spanX;
         launcherInfo.spanY = itemInfo.spanY;
         launcherInfo.minSpanX = itemInfo.minSpanX;
         launcherInfo.minSpanY = itemInfo.minSpanY;
-        launcherInfo.user = appWidgetInfo.getUser();
+        launcherInfo.user = appWidgetInfo.getProfile();
 
         getModelWriter().addItemToDatabase(launcherInfo,
                 itemInfo.container, itemInfo.screenId, itemInfo.cellX, itemInfo.cellY);
@@ -1538,15 +1239,10 @@
         public void onReceive(Context context, Intent intent) {
             final String action = intent.getAction();
             if (Intent.ACTION_SCREEN_OFF.equals(action)) {
-                mDragLayer.clearResizeFrame();
-
                 // Reset AllApps to its initial state only if we are not in the middle of
                 // processing a multi-step drop
-                if (mAppsView != null && mWidgetsView != null && mPendingRequestArgs == null) {
-                    if (!showWorkspace(false)) {
-                        // If we are already on the workspace, then manually reset all apps
-                        mAppsView.reset();
-                    }
+                if (mAppsView != null && mPendingRequestArgs == null) {
+                    mStateManager.goToState(NORMAL);
                 }
                 mShouldFadeInScrim = true;
             } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
@@ -1594,42 +1290,8 @@
         }
     }
 
-    public void onWindowVisibilityChanged(int visibility) {
-        // The following code used to be in onResume, but it turns out onResume is called when
-        // you're in All Apps and click home to go to the workspace. onWindowVisibilityChanged
-        // is a more appropriate event to handle
-        if (visibility == View.VISIBLE) {
-            if (!mWorkspaceLoading) {
-                final ViewTreeObserver observer = mWorkspace.getViewTreeObserver();
-                // We want to let Launcher draw itself at least once before we force it to build
-                // layers on all the workspace pages, so that transitioning to Launcher from other
-                // apps is nice and speedy.
-                observer.addOnDrawListener(new ViewTreeObserver.OnDrawListener() {
-                    private boolean mStarted = false;
-                    public void onDraw() {
-                        if (mStarted) return;
-                        mStarted = true;
-                        // We delay the layer building a bit in order to give
-                        // other message processing a time to run.  In particular
-                        // this avoids a delay in hiding the IME if it was
-                        // currently shown, because doing that may involve
-                        // some communication back with the app.
-                        mWorkspace.postDelayed(mBuildLayersRunnable, 500);
-                        final ViewTreeObserver.OnDrawListener listener = this;
-                        mWorkspace.post(new Runnable() {
-                            public void run() {
-                                if (mWorkspace != null &&
-                                        mWorkspace.getViewTreeObserver() != null) {
-                                    mWorkspace.getViewTreeObserver().
-                                            removeOnDrawListener(listener);
-                                }
-                            }
-                        });
-                    }
-                });
-            }
-            clearTypedText();
-        }
+    public AllAppsTransitionController getAllAppsController() {
+        return mAllAppsController;
     }
 
     public DragLayer getDragLayer() {
@@ -1640,10 +1302,6 @@
         return mAppsView;
     }
 
-    public WidgetsContainerView getWidgetsView() {
-        return mWidgetsView;
-    }
-
     public Workspace getWorkspace() {
         return mWorkspace;
     }
@@ -1652,8 +1310,8 @@
         return mHotseat;
     }
 
-    public ViewGroup getOverviewPanel() {
-        return mOverviewPanel;
+    public <T extends ViewGroup> T getOverviewPanel() {
+        return (T) mOverviewPanel;
     }
 
     public DropTargetBar getDropTargetBar() {
@@ -1680,10 +1338,7 @@
 
     @Override
     protected void onNewIntent(Intent intent) {
-        long startTime = 0;
-        if (DEBUG_RESUME_TIME) {
-            startTime = System.currentTimeMillis();
-        }
+        TraceHelper.beginSection("NEW_INTENT");
         super.onNewIntent(intent);
 
         boolean alreadyOnHome = mHasFocus && ((intent.getFlags() &
@@ -1691,101 +1346,52 @@
                 != Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
 
         // Check this condition before handling isActionMain, as this will get reset.
-        boolean shouldMoveToDefaultScreen = alreadyOnHome &&
-                mState == State.WORKSPACE && AbstractFloatingView.getTopOpenView(this) == null;
-
+        boolean shouldMoveToDefaultScreen = alreadyOnHome && isInState(NORMAL)
+                && AbstractFloatingView.getTopOpenView(this) == null;
         boolean isActionMain = Intent.ACTION_MAIN.equals(intent.getAction());
+        boolean internalStateHandled = InternalStateHandler
+                .handleNewIntent(this, intent, alreadyOnHome);
+
         if (isActionMain) {
-            if (mWorkspace == null) {
-                // Can be cases where mWorkspace is null, this prevents a NPE
-                return;
-            }
+            if (!internalStateHandled) {
+                // Note: There should be at most one log per method call. This is enforced
+                // implicitly by using if-else statements.
+                UserEventDispatcher ued = getUserEventDispatcher();
+                AbstractFloatingView topOpenView = AbstractFloatingView.getTopOpenView(this);
+                if (topOpenView != null) {
+                    topOpenView.logActionCommand(Action.Command.HOME_INTENT);
+                } else if (alreadyOnHome) {
+                    Target target = newContainerTarget(mStateManager.getState().containerType);
+                    target.pageIndex = mWorkspace.getCurrentPage();
+                    ued.logActionCommand(Action.Command.HOME_INTENT, target);
+                }
 
-            // Note: There should be at most one log per method call. This is enforced implicitly
-            // by using if-else statements.
-            UserEventDispatcher ued = getUserEventDispatcher();
+                // In all these cases, only animate if we're already on home
+                AbstractFloatingView.closeAllOpenViews(this, alreadyOnHome);
 
-            // TODO: Log this case.
-            mWorkspace.exitWidgetResizeMode();
+                mStateManager.goToState(NORMAL, alreadyOnHome /* animated */);
 
-            AbstractFloatingView topOpenView = AbstractFloatingView.getTopOpenView(this);
-            if (topOpenView instanceof PopupContainerWithArrow) {
-                ued.logActionCommand(Action.Command.HOME_INTENT,
-                        topOpenView.getExtendedTouchView(), ContainerType.DEEPSHORTCUTS);
-            } else if (topOpenView instanceof Folder) {
-                ued.logActionCommand(Action.Command.HOME_INTENT,
-                            ((Folder) topOpenView).getFolderIcon(), ContainerType.FOLDER);
-            } else if (alreadyOnHome) {
-                ued.logActionCommand(Action.Command.HOME_INTENT,
-                        mWorkspace.getState().containerType, mWorkspace.getCurrentPage());
-            }
+                // Reset the apps view
+                if (!alreadyOnHome && mAppsView != null) {
+                    mAppsView.reset();
+                }
 
-            // In all these cases, only animate if we're already on home
-            AbstractFloatingView.closeAllOpenViews(this, alreadyOnHome);
-            exitSpringLoadedDragMode();
-
-            // If we are already on home, then just animate back to the workspace,
-            // otherwise, just wait until onResume to set the state back to Workspace
-            if (alreadyOnHome) {
-                showWorkspace(true);
-            } else {
-                mOnResumeState = State.WORKSPACE;
+                if (shouldMoveToDefaultScreen && !mWorkspace.isTouchActive()) {
+                    mWorkspace.post(mWorkspace::moveToDefaultScreen);
+                }
             }
 
             final View v = getWindow().peekDecorView();
             if (v != null && v.getWindowToken() != null) {
-                InputMethodManager imm = (InputMethodManager) getSystemService(
-                        INPUT_METHOD_SERVICE);
-                imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
-            }
-
-            // Reset the apps view
-            if (!alreadyOnHome && mAppsView != null) {
-                mAppsView.reset();
-            }
-
-            // Reset the widgets view
-            if (!alreadyOnHome && mWidgetsView != null) {
-                mWidgetsView.scrollToTop();
+                UiThreadHelper.hideKeyboardAsync(this, v.getWindowToken());
             }
 
             if (mLauncherCallbacks != null) {
                 mLauncherCallbacks.onHomeIntent();
             }
         }
-        PinItemDragListener.handleDragRequest(this, intent);
 
-        if (mLauncherCallbacks != null) {
-            mLauncherCallbacks.onNewIntent(intent);
-        }
-
-        // Defer moving to the default screen until after we callback to the LauncherCallbacks
-        // as slow logic in the callbacks eat into the time the scroller expects for the snapToPage
-        // animation.
-        if (isActionMain) {
-            boolean callbackAllowsMoveToDefaultScreen =
-                mLauncherCallbacks == null || mLauncherCallbacks
-                    .shouldMoveToDefaultScreenOnHomeIntent();
-            if (shouldMoveToDefaultScreen && !mWorkspace.isTouchActive()
-                    && callbackAllowsMoveToDefaultScreen) {
-
-                // We use this flag to suppress noisy callbacks above custom content state
-                // from onResume.
-                mMoveToDefaultScreenFromNewIntent = true;
-                mWorkspace.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        if (mWorkspace != null) {
-                            mWorkspace.moveToDefaultScreen(true);
-                        }
-                    }
-                });
-            }
-        }
-
-        if (DEBUG_RESUME_TIME) {
-            Log.d(TAG, "Time spent in onNewIntent: " + (System.currentTimeMillis() - startTime));
-        }
+        TraceHelper.endSection("NEW_INTENT");
     }
 
     @Override
@@ -1799,13 +1405,22 @@
     @Override
     protected void onSaveInstanceState(Bundle outState) {
         if (mWorkspace.getChildCount() > 0) {
-            outState.putInt(RUNTIME_STATE_CURRENT_SCREEN,
-                    mWorkspace.getCurrentPageOffsetFromCustomContent());
+            outState.putInt(RUNTIME_STATE_CURRENT_SCREEN, mWorkspace.getNextPage());
 
         }
-        super.onSaveInstanceState(outState);
+        outState.putInt(RUNTIME_STATE, mStateManager.getState().ordinal);
 
-        outState.putInt(RUNTIME_STATE, mState.ordinal());
+
+        AbstractFloatingView widgets = AbstractFloatingView
+                .getOpenView(this, AbstractFloatingView.TYPE_WIDGETS_FULL_SHEET);
+        if (widgets != null) {
+            SparseArray<Parcelable> widgetsState = new SparseArray<>();
+            widgets.saveHierarchyState(widgetsState);
+            outState.putSparseParcelableArray(RUNTIME_STATE_WIDGET_PANEL, widgetsState);
+        } else {
+            outState.remove(RUNTIME_STATE_WIDGET_PANEL);
+        }
+
         // We close any open folders and shortcut containers since they will not be re-opened,
         // and we need to make sure this state is reflected.
         AbstractFloatingView.closeAllOpenViews(this, false);
@@ -1817,6 +1432,8 @@
             outState.putParcelable(RUNTIME_STATE_PENDING_ACTIVITY_RESULT, mPendingActivityResult);
         }
 
+        super.onSaveInstanceState(outState);
+
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.onSaveInstanceState(outState);
         }
@@ -1827,7 +1444,6 @@
         super.onDestroy();
 
         unregisterReceiver(mReceiver);
-        mWorkspace.removeCallbacks(mBuildLayersRunnable);
         mWorkspace.removeFolderListeners();
 
         // Stop callbacks from LauncherModel
@@ -1850,10 +1466,6 @@
         mAppWidgetHost = null;
 
         TextKeyListener.getInstance().release();
-
-        ((AccessibilityManager) getSystemService(ACCESSIBILITY_SERVICE))
-                .removeAccessibilityStateChangeListener(this);
-
         WallpaperColorInfo.getInstance(this).setOnThemeChangeListener(null);
 
         LauncherAnimUtils.onDestroyActivity();
@@ -1913,7 +1525,7 @@
         }
 
         // We need to show the workspace after starting the search
-        showWorkspace(true);
+        mStateManager.goToState(NORMAL);
     }
 
     /**
@@ -1956,10 +1568,6 @@
         }
     }
 
-    public boolean isOnCustomContent() {
-        return mWorkspace.isOnOrMovingToCustomContent();
-    }
-
     @Override
     public boolean onPrepareOptionsMenu(Menu menu) {
         super.onPrepareOptionsMenu(menu);
@@ -1985,25 +1593,11 @@
     }
 
     private void setWorkspaceLoading(boolean value) {
-        boolean isLocked = isWorkspaceLocked();
         mWorkspaceLoading = value;
-        if (isLocked != isWorkspaceLocked()) {
-            onWorkspaceLockedChanged();
-        }
     }
 
     public void setWaitingForResult(PendingRequestArgs args) {
-        boolean isLocked = isWorkspaceLocked();
         mPendingRequestArgs = args;
-        if (isLocked != isWorkspaceLocked()) {
-            onWorkspaceLockedChanged();
-        }
-    }
-
-    protected void onWorkspaceLockedChanged() {
-        if (mLauncherCallbacks != null) {
-            mLauncherCallbacks.onWorkspaceLockedChanged();
-        }
     }
 
     void addAppWidgetFromDropImpl(int appWidgetId, ItemInfo info, AppWidgetHostView boundWidget,
@@ -2023,8 +1617,7 @@
                 @Override
                 public void run() {
                     // Exit spring loaded mode if necessary after adding the widget
-                    exitSpringLoadedDragModeDelayed(true, EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT,
-                            null);
+                    mStateManager.goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
                 }
             };
             completeAddAppWidget(appWidgetId, info, boundWidget, addFlowHandler.getProviderInfo(this));
@@ -2032,12 +1625,6 @@
         }
     }
 
-    protected void moveToCustomContentScreen(boolean animate) {
-        // Close any folders that may be open.
-        AbstractFloatingView.closeAllOpenViews(this, animate);
-        mWorkspace.moveToCustomContentScreen(animate);
-    }
-
     public void addPendingItem(PendingAddItemInfo info, long container, long screenId,
             int[] cell, int spanX, int spanY) {
         info.container = container;
@@ -2078,7 +1665,7 @@
      */
     private void addAppWidgetFromDrop(PendingAddWidgetInfo info) {
         AppWidgetHostView hostView = info.boundWidget;
-        int appWidgetId;
+        final int appWidgetId;
         WidgetAddFlowHandler addFlowHandler = info.getHandler();
         if (hostView != null) {
             // In the case where we've prebound the widget, we remove it from the DragLayer
@@ -2095,7 +1682,13 @@
         } else {
             // In this case, we either need to start an activity to get permission to bind
             // the widget, or we need to start an activity to configure the widget, or both.
-            appWidgetId = getAppWidgetHost().allocateAppWidgetId();
+            if (FeatureFlags.ENABLE_CUSTOM_WIDGETS &&
+                    info.itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET) {
+                appWidgetId = CustomWidgetParser.getWidgetIdForCustomProvider(
+                        this, info.componentName);
+            } else {
+                appWidgetId = getAppWidgetHost().allocateAppWidgetId();
+            }
             Bundle options = info.bindOptions;
 
             boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(
@@ -2204,31 +1797,11 @@
         UserEventDispatcher ued = getUserEventDispatcher();
         AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(this);
         if (topView != null) {
-            if (topView.getActiveTextView() != null) {
-                topView.getActiveTextView().dispatchBackKey();
-            } else {
-                if (topView instanceof PopupContainerWithArrow) {
-                    ued.logActionCommand(Action.Command.BACK,
-                            topView.getExtendedTouchView(), ContainerType.DEEPSHORTCUTS);
-                } else if (topView instanceof Folder) {
-                    ued.logActionCommand(Action.Command.BACK,
-                            ((Folder) topView).getFolderIcon(), ContainerType.FOLDER);
-                }
-                topView.close(true);
-            }
-        } else if (isAppsViewVisible()) {
-            ued.logActionCommand(Action.Command.BACK, ContainerType.ALLAPPS);
-            showWorkspace(true);
-        } else if (isWidgetsViewVisible())  {
-            ued.logActionCommand(Action.Command.BACK, ContainerType.WIDGETS);
-            showOverviewMode(true);
-        } else if (mWorkspace.isInOverviewMode()) {
-            ued.logActionCommand(Action.Command.BACK, ContainerType.OVERVIEW);
-            showWorkspace(true);
+            topView.onBackPressed();
+        } else if (!isInState(NORMAL)) {
+            ued.logActionCommand(Action.Command.BACK, mStateManager.getState().containerType);
+            mStateManager.goToState(NORMAL);
         } else {
-            // TODO: Log this case.
-            mWorkspace.exitWidgetResizeMode();
-
             // Back button is a no-op here, but give at least some feedback for the button press
             mWorkspace.showOutlinesTemporarily();
         }
@@ -2251,23 +1824,23 @@
         }
 
         if (v instanceof Workspace) {
-            if (mWorkspace.isInOverviewMode()) {
+            if (isInState(OVERVIEW)) {
                 getUserEventDispatcher().logActionOnContainer(LauncherLogProto.Action.Type.TOUCH,
                         LauncherLogProto.Action.Direction.NONE,
                         LauncherLogProto.ContainerType.OVERVIEW, mWorkspace.getCurrentPage());
-                showWorkspace(true);
+                mStateManager.goToState(NORMAL);
             }
             return;
         }
 
         if (v instanceof CellLayout) {
-            if (mWorkspace.isInOverviewMode()) {
+            if (isInState(OVERVIEW)) {
                 int page = mWorkspace.indexOfChild(v);
                 getUserEventDispatcher().logActionOnContainer(LauncherLogProto.Action.Type.TOUCH,
                         LauncherLogProto.Action.Direction.NONE,
                         LauncherLogProto.ContainerType.OVERVIEW, page);
                 mWorkspace.snapToPageFromOverView(page);
-                showWorkspace(true);
+                mStateManager.goToState(NORMAL);
             }
             return;
         }
@@ -2339,12 +1912,12 @@
      */
     protected void onClickAllAppsButton(View v) {
         if (LOGD) Log.d(TAG, "onClickAllAppsButton");
-        if (!isAppsViewVisible()) {
+        if (!isInState(ALL_APPS)) {
             getUserEventDispatcher().logActionOnControl(Action.Touch.TAP,
                     ControlType.ALL_APPS_BUTTON);
-            showAppsView(true /* animated */, true /* updatePredictedApps */);
+            mStateManager.goToState(ALL_APPS);
         } else {
-            showWorkspace(true);
+            mStateManager.goToState(NORMAL);
         }
     }
 
@@ -2377,11 +1950,7 @@
     private void startMarketIntentForPackage(View v, String packageName) {
         ItemInfo item = (ItemInfo) v.getTag();
         Intent intent = PackageManagerHelper.getMarketIntent(packageName);
-        boolean success = startActivitySafely(v, intent, item);
-        if (success && v instanceof BubbleTextView) {
-            mWaitingForResume = (BubbleTextView) v;
-            mWaitingForResume.setStayPressed(true);
-        }
+        startActivitySafely(v, intent, item);
     }
 
     /**
@@ -2399,10 +1968,10 @@
         // Open shortcut
         final ShortcutInfo shortcut = (ShortcutInfo) tag;
 
-        if (shortcut.isDisabled != 0) {
-            if ((shortcut.isDisabled &
-                    ~ShortcutInfo.FLAG_DISABLED_SUSPENDED &
-                    ~ShortcutInfo.FLAG_DISABLED_QUIET_USER) == 0) {
+        if (shortcut.isDisabled()) {
+            if ((shortcut.runtimeStatusFlags &
+                    ~FLAG_DISABLED_SUSPENDED &
+                    ~FLAG_DISABLED_QUIET_USER) == 0) {
                 // If the app is only disabled because of the above flags, launch activity anyway.
                 // Framework will tell the user why the app is suspended.
             } else {
@@ -2413,10 +1982,10 @@
                 }
                 // Otherwise just use a generic error message.
                 int error = R.string.activity_not_available;
-                if ((shortcut.isDisabled & ShortcutInfo.FLAG_DISABLED_SAFEMODE) != 0) {
+                if ((shortcut.runtimeStatusFlags & FLAG_DISABLED_SAFEMODE) != 0) {
                     error = R.string.safemode_shortcut_error;
-                } else if ((shortcut.isDisabled & ShortcutInfo.FLAG_DISABLED_BY_PUBLISHER) != 0 ||
-                        (shortcut.isDisabled & ShortcutInfo.FLAG_DISABLED_LOCKED_USER) != 0) {
+                } else if ((shortcut.runtimeStatusFlags & FLAG_DISABLED_BY_PUBLISHER) != 0 ||
+                        (shortcut.runtimeStatusFlags & FLAG_DISABLED_LOCKED_USER) != 0) {
                     error = R.string.shortcut_not_available;
                 }
                 Toast.makeText(this, error, Toast.LENGTH_SHORT).show();
@@ -2451,13 +2020,7 @@
         if (intent == null) {
             throw new IllegalArgumentException("Input must have a valid intent");
         }
-        boolean success = startActivitySafely(v, intent, item);
-        getUserEventDispatcher().logAppLaunch(v, intent); // TODO for discovered apps b/35802115
-
-        if (success && v instanceof BubbleTextView) {
-            mWaitingForResume = (BubbleTextView) v;
-            mWaitingForResume.setStayPressed(true);
-        }
+        startActivitySafely(v, intent, item);
     }
 
     /**
@@ -2479,19 +2042,6 @@
     }
 
     /**
-     * Event handler for the (Add) Widgets button that appears after a long press
-     * on the home screen.
-     */
-    public void onClickAddWidgetButton(View view) {
-        if (LOGD) Log.d(TAG, "onClickAddWidgetButton");
-        if (mIsSafeModeEnabled) {
-            Toast.makeText(this, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
-        } else {
-            showWidgetsView(true /* animated */, true /* resetPageToZero */);
-        }
-    }
-
-    /**
      * Event handler for the wallpaper picker button that appears after a long press
      * on the home screen.
      */
@@ -2513,81 +2063,22 @@
             intent.setPackage(pickerPackage);
         }
 
-        intent.setSourceBounds(getViewBounds(v));
+        final Bundle launchOptions;
+        if (v != null) {
+            intent.setSourceBounds(getViewBounds(v));
+            // If there is no target package, use the default intent chooser animation
+            launchOptions = hasTargetPackage ? getActivityLaunchOptions(v) : null;
+        } else {
+            launchOptions = null;
+        }
         try {
-            startActivityForResult(intent, REQUEST_PICK_WALLPAPER,
-                    // If there is no target package, use the default intent chooser animation
-                    hasTargetPackage ? getActivityLaunchOptions(v) : null);
+            startActivityForResult(intent, REQUEST_PICK_WALLPAPER, launchOptions);
         } catch (ActivityNotFoundException e) {
             setWaitingForResult(null);
             Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
         }
     }
 
-    /**
-     * Event handler for a click on the settings button that appears after a long press
-     * on the home screen.
-     */
-    public void onClickSettingsButton(View v) {
-        if (LOGD) Log.d(TAG, "onClickSettingsButton");
-        Intent intent = new Intent(Intent.ACTION_APPLICATION_PREFERENCES)
-                .setPackage(getPackageName());
-        intent.setSourceBounds(getViewBounds(v));
-        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        startActivity(intent, getActivityLaunchOptions(v));
-    }
-
-    @Override
-    public void onAccessibilityStateChanged(boolean enabled) {
-        mDragLayer.onAccessibilityStateChanged(enabled);
-    }
-
-    public void onDragStarted() {
-        if (isOnCustomContent()) {
-            // Custom content screen doesn't participate in drag and drop. If on custom
-            // content screen, move to default.
-            moveWorkspaceToDefaultScreen();
-        }
-    }
-
-    /**
-     * Called when the user stops interacting with the launcher.
-     * This implies that the user is now on the homescreen and is not doing housekeeping.
-     */
-    protected void onInteractionEnd() {
-        if (mLauncherCallbacks != null) {
-            mLauncherCallbacks.onInteractionEnd();
-        }
-    }
-
-    /**
-     * Called when the user starts interacting with the launcher.
-     * The possible interactions are:
-     *  - open all apps
-     *  - reorder an app shortcut, or a widget
-     *  - open the overview mode.
-     * This is a good time to stop doing things that only make sense
-     * when the user is on the homescreen and not doing housekeeping.
-     */
-    protected void onInteractionBegin() {
-        if (mLauncherCallbacks != null) {
-            mLauncherCallbacks.onInteractionBegin();
-        }
-    }
-
-    /** Updates the interaction state. */
-    public void updateInteraction(Workspace.State fromState, Workspace.State toState) {
-        // Only update the interacting state if we are transitioning to/from a view with an
-        // overlay
-        boolean fromStateWithOverlay = fromState != Workspace.State.NORMAL;
-        boolean toStateWithOverlay = toState != Workspace.State.NORMAL;
-        if (toStateWithOverlay) {
-            onInteractionBegin();
-        } else if (fromStateWithOverlay) {
-            onInteractionEnd();
-        }
-    }
-
     private void startShortcutIntentSafely(Intent intent, Bundle optsBundle, ItemInfo info) {
         try {
             StrictMode.VmPolicy oldPolicy = StrictMode.getVmPolicy();
@@ -2664,9 +2155,10 @@
     }
 
     public boolean startActivitySafely(View v, Intent intent, ItemInfo item) {
+        mAppLaunchSuccess = false;
         if (mIsSafeModeEnabled && !Utilities.isSystemApp(this, intent)) {
             Toast.makeText(this, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show();
-            return false;
+            return mAppLaunchSuccess;
         }
         // Only launch using the new animation if the shortcut has not opted out (this is a
         // private contract between launcher and may be ignored in the future).
@@ -2696,12 +2188,23 @@
                 LauncherAppsCompat.getInstance(this).startActivityForProfile(
                         intent.getComponent(), user, intent.getSourceBounds(), optsBundle);
             }
-            return true;
+
+            if (v instanceof BubbleTextView) {
+                // This is set to the view that launched the activity that navigated the user away
+                // from launcher. Since there is no callback for when the activity has finished
+                // launching, enable the press state and keep this reference to reset the press
+                // state when we return to launcher.
+                BubbleTextView btv = (BubbleTextView) v;
+                btv.setStayPressed(true);
+                setOnResumeCallback(btv);
+            }
+            mAppLaunchSuccess = true;
+            getUserEventDispatcher().logAppLaunch(v, intent); // TODO for discovered apps b/35802115
         } catch (ActivityNotFoundException|SecurityException e) {
             Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
             Log.e(TAG, "Unable to launch. tag=" + item + " intent=" + intent, e);
         }
-        return false;
+        return mAppLaunchSuccess;
     }
 
     @Override
@@ -2714,18 +2217,18 @@
     public boolean onLongClick(View v) {
         if (!isDraggingEnabled()) return false;
         if (isWorkspaceLocked()) return false;
-        if (mState != State.WORKSPACE) return false;
+        if (!isInState(NORMAL) && !isInState(OVERVIEW)) return false;
 
         boolean ignoreLongPressToOverview =
                 mDeviceProfile.shouldIgnoreLongPressToOverview(mLastDispatchTouchEventX);
 
         if (v instanceof Workspace) {
-            if (!mWorkspace.isInOverviewMode()) {
+            if (!isInState(OVERVIEW)) {
                 if (!mWorkspace.isTouchActive() && !ignoreLongPressToOverview) {
                     getUserEventDispatcher().logActionOnContainer(Action.Touch.LONGPRESS,
                             Action.Direction.NONE, ContainerType.WORKSPACE,
                             mWorkspace.getCurrentPage());
-                    showOverviewMode(true);
+                    UiFactory.onWorkspaceLongPress(this);
                     mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
                             HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
                     return true;
@@ -2751,7 +2254,7 @@
         if (!mDragController.isDragging()) {
             if (itemUnderLongClick == null) {
                 // User long pressed on empty space
-                if (mWorkspace.isInOverviewMode()) {
+                if (mWorkspace.isPageRearrangeEnabled()) {
                     mWorkspace.startReordering(v);
                     getUserEventDispatcher().logActionOnContainer(Action.Touch.LONGPRESS,
                             Action.Direction.NONE, ContainerType.OVERVIEW);
@@ -2762,7 +2265,7 @@
                     getUserEventDispatcher().logActionOnContainer(Action.Touch.LONGPRESS,
                             Action.Direction.NONE, ContainerType.WORKSPACE,
                             mWorkspace.getCurrentPage());
-                    showOverviewMode(true);
+                    UiFactory.onWorkspaceLongPress(this);
                 }
                 mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
                         HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
@@ -2801,21 +2304,6 @@
         }
     }
 
-    /**
-     * For overridden classes.
-     */
-    public boolean isAllAppsVisible() {
-        return isAppsViewVisible();
-    }
-
-    public boolean isAppsViewVisible() {
-        return (mState == State.APPS) || (mOnResumeState == State.APPS);
-    }
-
-    public boolean isWidgetsViewVisible() {
-        return (mState == State.WIDGETS) || (mOnResumeState == State.WIDGETS);
-    }
-
     @Override
     public void onTrimMemory(int level) {
         super.onTrimMemory(level);
@@ -2832,257 +2320,16 @@
         }
     }
 
-    public boolean showWorkspace(boolean animated) {
-        return showWorkspace(animated, null);
-    }
-
-    public boolean showWorkspace(boolean animated, Runnable onCompleteRunnable) {
-        boolean changed = mState != State.WORKSPACE ||
-                mWorkspace.getState() != Workspace.State.NORMAL;
-        if (changed || mAllAppsController.isTransitioning()) {
-            mWorkspace.setVisibility(View.VISIBLE);
-            mStateTransitionAnimation.startAnimationToWorkspace(mState, mWorkspace.getState(),
-                    Workspace.State.NORMAL, animated, onCompleteRunnable);
-
-            // Set focus to the AppsCustomize button
-            if (mAllAppsButton != null) {
-                mAllAppsButton.requestFocus();
-            }
-        }
-
-        // Change the state *after* we've called all the transition code
-        setState(State.WORKSPACE);
-
-        if (changed) {
-            // Send an accessibility event to announce the context change
-            getWindow().getDecorView()
-                    .sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
-        }
-        return changed;
-    }
-
-    /**
-     * Shows the overview button.
-     */
-    public void showOverviewMode(boolean animated) {
-        showOverviewMode(animated, false);
-    }
-
-    /**
-     * Shows the overview button, and if {@param requestButtonFocus} is set, will force the focus
-     * onto one of the overview panel buttons.
-     */
-    void showOverviewMode(boolean animated, boolean requestButtonFocus) {
-        Runnable postAnimRunnable = null;
-        if (requestButtonFocus) {
-            postAnimRunnable = new Runnable() {
-                @Override
-                public void run() {
-                    // Hitting the menu button when in touch mode does not trigger touch mode to
-                    // be disabled, so if requested, force focus on one of the overview panel
-                    // buttons.
-                    mOverviewPanel.requestFocusFromTouch();
-                }
-            };
-        }
-        mWorkspace.setVisibility(View.VISIBLE);
-        mStateTransitionAnimation.startAnimationToWorkspace(mState, mWorkspace.getState(),
-                Workspace.State.OVERVIEW, animated, postAnimRunnable);
-        setState(State.WORKSPACE);
-
-        // If animated from long press, then don't allow any of the controller in the drag
-        // layer to intercept any remaining touch.
-        mWorkspace.requestDisallowInterceptTouchEvent(animated);
-    }
-
-    private void setState(State state) {
-        this.mState = state;
-        updateSoftInputMode();
-    }
-
-    private void updateSoftInputMode() {
-        if (FeatureFlags.LAUNCHER3_UPDATE_SOFT_INPUT_MODE) {
-            final int mode;
-            if (isAppsViewVisible()) {
-                mode = SOFT_INPUT_MODE_ALL_APPS;
-            } else {
-                mode = SOFT_INPUT_MODE_DEFAULT;
-            }
-            getWindow().setSoftInputMode(mode);
-        }
-    }
-
-    /**
-     * Shows the apps view.
-     */
-    public void showAppsView(boolean animated, boolean updatePredictedApps) {
-        markAppsViewShown();
-        if (updatePredictedApps) {
-            tryAndUpdatePredictedApps();
-        }
-        showAppsOrWidgets(State.APPS, animated);
-    }
-
-    /**
-     * Shows the widgets view.
-     */
-    void showWidgetsView(boolean animated, boolean resetPageToZero) {
-        if (LOGD) Log.d(TAG, "showWidgetsView:" + animated + " resetPageToZero:" + resetPageToZero);
-        if (resetPageToZero) {
-            mWidgetsView.scrollToTop();
-        }
-        showAppsOrWidgets(State.WIDGETS, animated);
-
-        mWidgetsView.post(new Runnable() {
-            @Override
-            public void run() {
-                mWidgetsView.requestFocus();
-            }
-        });
-    }
-
-    /**
-     * Sets up the transition to show the apps/widgets view.
-     *
-     * @return whether the current from and to state allowed this operation
-     */
-    // TODO: calling method should use the return value so that when {@code false} is returned
-    // the workspace transition doesn't fall into invalid state.
-    private boolean showAppsOrWidgets(State toState, boolean animated) {
-        if (!(mState == State.WORKSPACE ||
-                mState == State.APPS_SPRING_LOADED ||
-                mState == State.WIDGETS_SPRING_LOADED ||
-                (mState == State.APPS && mAllAppsController.isTransitioning()))) {
-            return false;
-        }
-        if (toState != State.APPS && toState != State.WIDGETS) {
-            return false;
-        }
-
-        // This is a safe and supported transition to bypass spring_loaded mode.
-        if (mExitSpringLoadedModeRunnable != null) {
-            mHandler.removeCallbacks(mExitSpringLoadedModeRunnable);
-            mExitSpringLoadedModeRunnable = null;
-        }
-
-        if (toState == State.APPS) {
-            mStateTransitionAnimation.startAnimationToAllApps(animated);
-        } else {
-            mStateTransitionAnimation.startAnimationToWidgets(animated);
-        }
-
-        // Change the state *after* we've called all the transition code
-        setState(toState);
-        AbstractFloatingView.closeAllOpenViews(this);
-
-        // Send an accessibility event to announce the context change
-        getWindow().getDecorView()
-                .sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
-        return true;
-    }
-
-    /**
-     * Updates the workspace and interaction state on state change, and return the animation to this
-     * new state.
-     */
-    public Animator startWorkspaceStateChangeAnimation(Workspace.State toState,
-            boolean animated, AnimationLayerSet layerViews) {
-        Workspace.State fromState = mWorkspace.getState();
-        Animator anim = mWorkspace.setStateWithAnimation(toState, animated, layerViews);
-        updateInteraction(fromState, toState);
-        return anim;
-    }
-
-    public void enterSpringLoadedDragMode() {
-        if (LOGD) Log.d(TAG, String.format("enterSpringLoadedDragMode [mState=%s", mState.name()));
-        if (isStateSpringLoaded()) {
-            return;
-        }
-
-        mStateTransitionAnimation.startAnimationToWorkspace(mState, mWorkspace.getState(),
-                Workspace.State.SPRING_LOADED, true /* animated */,
-                null /* onCompleteRunnable */);
-        setState(State.WORKSPACE_SPRING_LOADED);
-    }
-
-    public void exitSpringLoadedDragModeDelayed(final boolean successfulDrop, int delay,
-            final Runnable onCompleteRunnable) {
-        if (!isStateSpringLoaded()) return;
-
-        if (mExitSpringLoadedModeRunnable != null) {
-            mHandler.removeCallbacks(mExitSpringLoadedModeRunnable);
-        }
-        mExitSpringLoadedModeRunnable = new Runnable() {
-            @Override
-            public void run() {
-                if (successfulDrop) {
-                    // TODO(hyunyoungs): verify if this hack is still needed, if not, delete.
-                    //
-                    // Before we show workspace, hide all apps again because
-                    // exitSpringLoadedDragMode made it visible. This is a bit hacky; we should
-                    // clean up our state transition functions
-                    mWidgetsView.setVisibility(View.GONE);
-                    showWorkspace(true, onCompleteRunnable);
-                } else {
-                    exitSpringLoadedDragMode();
-                }
-                mExitSpringLoadedModeRunnable = null;
-            }
-        };
-        mHandler.postDelayed(mExitSpringLoadedModeRunnable, delay);
-    }
-
-    boolean isStateSpringLoaded() {
-        return mState == State.WORKSPACE_SPRING_LOADED || mState == State.APPS_SPRING_LOADED
-                || mState == State.WIDGETS_SPRING_LOADED;
-    }
-
-    public void exitSpringLoadedDragMode() {
-        if (mState == State.APPS_SPRING_LOADED) {
-            showAppsView(true /* animated */, false /* updatePredictedApps */);
-        } else if (mState == State.WIDGETS_SPRING_LOADED) {
-            showWidgetsView(true, false);
-        } else if (mState == State.WORKSPACE_SPRING_LOADED) {
-            showWorkspace(true);
-        }
-    }
-
-    /**
-     * Updates the set of predicted apps if it hasn't been updated since the last time Launcher was
-     * resumed.
-     */
-    public void tryAndUpdatePredictedApps() {
-        if (mLauncherCallbacks != null) {
-            List<ComponentKeyMapper<AppInfo>> apps = mLauncherCallbacks.getPredictedApps();
-            if (apps != null) {
-                mAppsView.setPredictedApps(apps);
-            }
-        }
-    }
-
-    void lockAllApps() {
-        // TODO
-    }
-
-    void unlockAllApps() {
-        // TODO
-    }
-
     @Override
     public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
         final boolean result = super.dispatchPopulateAccessibilityEvent(event);
         final List<CharSequence> text = event.getText();
         text.clear();
         // Populate event with a fake title based on the current state.
-        if (mState == State.APPS) {
-            text.add(getString(R.string.all_apps_button_label));
-        } else if (mState == State.WIDGETS) {
-            text.add(getString(R.string.widget_button_text));
-        } else if (mWorkspace != null) {
-            text.add(mWorkspace.getCurrentPageDescription());
-        } else {
-            text.add(getString(R.string.all_apps_home_button_label));
-        }
+        // TODO: When can workspace be null?
+        text.add(mWorkspace == null
+                ? getString(R.string.all_apps_home_button_label)
+                : mStateManager.getState().getDescription(this));
         return result;
     }
 
@@ -3113,8 +2360,11 @@
         }
     }
 
-    public void addOnResumeCallback(Runnable run) {
-        mOnResumeCallbacks.add(run);
+    public void setOnResumeCallback(OnResumeCallback callback) {
+        if (mOnResumeCallback != null) {
+            mOnResumeCallback.onLauncherResume();
+        }
+        mOnResumeCallback = callback;
     }
 
     /**
@@ -3173,11 +2423,12 @@
      * Implementation of the method from LauncherModel.Callbacks.
      */
     public void startBinding() {
-        if (LauncherAppState.PROFILE_STARTUP) {
-            Trace.beginSection("Starting page bind");
-        }
-
-        AbstractFloatingView.closeAllOpenViews(this);
+        TraceHelper.beginSection("startBinding");
+        // Floating panels (except the full widget sheet) are associated with individual icons. If
+        // we are starting a fresh bind, close all such panels as all the icons are about
+        // to go away.
+        AbstractFloatingView.closeOpenViews(this, true,
+                AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_WIDGETS_FULL_SHEET);
 
         setWorkspaceLoading(true);
 
@@ -3188,9 +2439,7 @@
         if (mHotseat != null) {
             mHotseat.resetLayout();
         }
-        if (LauncherAppState.PROFILE_STARTUP) {
-            Trace.endSection();
-        }
+        TraceHelper.endSection("startBinding");
     }
 
     @Override
@@ -3207,13 +2456,6 @@
         }
         bindAddScreens(orderedScreenIds);
 
-        // Create the custom content page (this call updates mDefaultScreen which calls
-        // setCurrentPage() so ensure that all pages are added before calling this).
-        if (hasCustomContentToLeft()) {
-            mWorkspace.createCustomContentContainer();
-            populateCustomContentContainer();
-        }
-
         // After we have added all the screens, if the wallpaper was locked to the default state,
         // then notify to indicate that it can be released and a proper wallpaper offset can be
         // computed before the next layout
@@ -3309,7 +2551,8 @@
                             (FolderInfo) item);
                     break;
                 }
-                case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: {
+                case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
+                case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET: {
                     view = inflateAppWidget((LauncherAppWidgetInfo) item);
                     if (view == null) {
                         continue;
@@ -3400,10 +2643,7 @@
             return view;
         }
 
-        final long start = DEBUG_WIDGETS ? SystemClock.uptimeMillis() : 0;
-        if (DEBUG_WIDGETS) {
-            Log.d(TAG, "bindAppWidget: " + item);
-        }
+        TraceHelper.beginSection("BIND_WIDGET");
 
         final LauncherAppWidgetProviderInfo appWidgetInfo;
 
@@ -3421,11 +2661,9 @@
         if (!item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) &&
                 (item.restoreStatus != LauncherAppWidgetInfo.RESTORE_COMPLETED)) {
             if (appWidgetInfo == null) {
-                if (DEBUG_WIDGETS) {
-                    Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId
-                            + " belongs to component " + item.providerName
-                            + ", as the provider is null");
-                }
+                Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId
+                        + " belongs to component " + item.providerName
+                        + ", as the provider is null");
                 getModelWriter().deleteItemFromDatabase(item);
                 return null;
             }
@@ -3486,11 +2724,6 @@
 
         final AppWidgetHostView view;
         if (item.restoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED) {
-            if (DEBUG_WIDGETS) {
-                Log.d(TAG, "bindAppWidget: id=" + item.appWidgetId + " belongs to component "
-                        + appWidgetInfo.provider);
-            }
-
             // Verify that we own the widget
             if (appWidgetInfo == null) {
                 FileLog.e(TAG, "Removing invalid widget: id=" + item.appWidgetId);
@@ -3506,10 +2739,7 @@
         }
         prepareAppWidget(view, item);
 
-        if (DEBUG_WIDGETS) {
-            Log.d(TAG, "bound widget id="+item.appWidgetId+" in "
-                    + (SystemClock.uptimeMillis()-start) + "ms");
-        }
+        TraceHelper.endSection("BIND_WIDGET", "id=" + item.appWidgetId);
         return view;
     }
 
@@ -3531,7 +2761,10 @@
             info.pendingItemInfo = null;
         }
 
-        mWorkspace.reinflateWidgetsIfNecessary();
+        if (((PendingAppWidgetHostView) view).isReinflateIfNeeded()) {
+            view.reinflate();
+        }
+
         getModelWriter().updateItemInDatabase(info);
         return info;
     }
@@ -3595,9 +2828,7 @@
         if (waitUntilResume(r)) {
             return;
         }
-        if (LauncherAppState.PROFILE_STARTUP) {
-            Trace.beginSection("Page bind completed");
-        }
+        TraceHelper.beginSection("finishBindingItems");
         mWorkspace.restoreInstanceStateForRemainingPages();
 
         setWorkspaceLoading(false);
@@ -3612,13 +2843,7 @@
                 InstallShortcutReceiver.FLAG_LOADER_RUNNING, this);
 
         NotificationListener.setNotificationsChangedListener(mPopupDataProvider);
-
-        if (mLauncherCallbacks != null) {
-            mLauncherCallbacks.finishBindingItems(false);
-        }
-        if (LauncherAppState.PROFILE_STARTUP) {
-            Trace.endSection();
-        }
+        TraceHelper.endSection("finishBindingItems");
     }
 
     private boolean canRunNewAppsAnimation() {
@@ -3638,13 +2863,6 @@
         return mDeviceProfile.isVerticalBarLayout();
     }
 
-    public int getSearchBarHeight() {
-        if (mLauncherCallbacks != null) {
-            return mLauncherCallbacks.getSearchBarHeight();
-        }
-        return LauncherCallbacks.SEARCH_BAR_HEIGHT_NORMAL;
-    }
-
     /**
      * Add the icons for all apps.
      *
@@ -3662,7 +2880,7 @@
 
         if (mAppsView != null) {
             Executor pendingExecutor = getPendingExecutor();
-            if (pendingExecutor != null && mState != State.APPS) {
+            if (pendingExecutor != null && !isInState(ALL_APPS)) {
                 // Wait until the fade in animation has finished before setting all apps list.
                 pendingExecutor.execute(r);
                 return;
@@ -3817,12 +3035,12 @@
         // Update AllApps
         if (mAppsView != null) {
             mAppsView.removeApps(appInfos);
-            tryAndUpdatePredictedApps();
         }
     }
 
     @Override
-    public void bindAllWidgets(final MultiHashMap<PackageItemInfo, WidgetItem> allWidgets) {
+    public void bindAllWidgets(final ArrayList<WidgetListRowEntry> allWidgets) {
+        mPopupDataProvider.setAllWidgets(allWidgets);
         Runnable r = new RunnableWithId(RUNNABLE_ID_BIND_WIDGETS) {
             @Override
             public void run() {
@@ -3833,32 +3051,12 @@
             return;
         }
 
-        if (mWidgetsView != null && allWidgets != null) {
-            Executor pendingExecutor = getPendingExecutor();
-            if (pendingExecutor != null && mState != State.WIDGETS) {
-                pendingExecutor.execute(r);
-                return;
-            }
-            mWidgetsView.setWidgets(allWidgets);
-        }
-
         AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(this);
         if (topView != null) {
             topView.onWidgetsBound();
         }
     }
 
-    public List<WidgetItem> getWidgetsForPackageUser(PackageUserKey packageUserKey) {
-        return mWidgetsView.getWidgetsForPackageUser(packageUserKey);
-    }
-
-    @Override
-    public void notifyWidgetProvidersChanged() {
-        if (mWorkspace.getState().shouldUpdateWidget) {
-            refreshAndBindWidgetsForPackageUser(null);
-        }
-    }
-
     /**
      * @param packageUser if null, refreshes all widgets and shortcuts, otherwise only
      *                    refreshes the widgets and shortcuts associated with the given package/user
@@ -3867,40 +3065,14 @@
         mModel.refreshAndBindWidgetsAndShortcuts(packageUser);
     }
 
-    public void lockScreenOrientation() {
-        if (mRotationEnabled) {
-            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
-        }
-    }
-
-    public void unlockScreenOrientation(boolean immediate) {
-        if (mRotationEnabled) {
-            if (immediate) {
-                setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
-            } else {
-                mHandler.postDelayed(new Runnable() {
-                    public void run() {
-                        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
-                    }
-                }, RESTORE_SCREEN_ORIENTATION_DELAY);
-            }
-        }
-    }
-
-    private void markAppsViewShown() {
-        if (mSharedPrefs.getBoolean(APPS_VIEW_SHOWN, false)) {
-            return;
-        }
-        mSharedPrefs.edit().putBoolean(APPS_VIEW_SHOWN, true).apply();
+    public boolean isRotationEnabled () {
+        return mRotationEnabled;
     }
 
     private boolean shouldShowDiscoveryBounce() {
-        UserManagerCompat um = UserManagerCompat.getInstance(this);
-        return mState == State.WORKSPACE && !mSharedPrefs.getBoolean(APPS_VIEW_SHOWN, false) && !um.isDemoUser();
-    }
-
-    protected void moveWorkspaceToDefaultScreen() {
-        mWorkspace.moveToDefaultScreen(false);
+        return isInState(NORMAL)
+                && !mSharedPrefs.getBoolean(AllAppsState.APPS_VIEW_SHOWN, false)
+                && !UserManagerCompat.getInstance(this).isDemoUser();
     }
 
     /**
@@ -3912,7 +3084,7 @@
 
         if (args.length > 0 && TextUtils.equals(args[0], "--all")) {
             writer.println(prefix + "Workspace Items");
-            for (int i = mWorkspace.numCustomPages(); i < mWorkspace.getPageCount(); i++) {
+            for (int i = 0; i < mWorkspace.getPageCount(); i++) {
                 writer.println(prefix + "  Homescreen " + i);
 
                 ViewGroup layout = ((CellLayout) mWorkspace.getPageAt(i)).getShortcutsAndWidgets();
@@ -3958,7 +3130,7 @@
             List<KeyboardShortcutGroup> data, Menu menu, int deviceId) {
 
         ArrayList<KeyboardShortcutInfo> shortcutInfos = new ArrayList<>();
-        if (mState == State.WORKSPACE) {
+        if (isInState(NORMAL)) {
             shortcutInfos.add(new KeyboardShortcutInfo(getString(R.string.all_apps_button_label),
                     KeyEvent.KEYCODE_A, KeyEvent.META_CTRL_ON));
         }
@@ -3984,8 +3156,8 @@
         if (event.hasModifiers(KeyEvent.META_CTRL_ON)) {
             switch (keyCode) {
                 case KeyEvent.KEYCODE_A:
-                    if (mState == State.WORKSPACE) {
-                        showAppsView(true, true);
+                    if (isInState(NORMAL)) {
+                        getStateManager().goToState(ALL_APPS);
                         return true;
                     }
                     break;
@@ -4011,14 +3183,6 @@
         return super.onKeyShortcut(keyCode, event);
     }
 
-    public static CustomAppWidget getCustomAppWidget(String name) {
-        return sCustomAppWidgets.get(name);
-    }
-
-    public static HashMap<String, CustomAppWidget> getCustomAppWidgets() {
-        return sCustomAppWidgets;
-    }
-
     public static Launcher getLauncher(Context context) {
         if (context instanceof Launcher) {
             return (Launcher) context;
@@ -4037,4 +3201,12 @@
             }
         }
     }
+
+    /**
+     * Callback for listening for onResume
+     */
+    public interface OnResumeCallback {
+
+        void onLauncherResume();
+    }
 }
diff --git a/src/com/android/launcher3/LauncherAnimUtils.java b/src/com/android/launcher3/LauncherAnimUtils.java
index cfb9b57..9869fdf 100644
--- a/src/com/android/launcher3/LauncherAnimUtils.java
+++ b/src/com/android/launcher3/LauncherAnimUtils.java
@@ -30,6 +30,15 @@
 import java.util.WeakHashMap;
 
 public class LauncherAnimUtils {
+    /**
+     * Durations for various state animations. These are not defined in resources to allow
+     * easier access from static classes and enums
+     */
+    public static final int ALL_APPS_TRANSITION_MS = 320;
+    public static final int OVERVIEW_TRANSITION_MS = 250;
+    public static final int SPRING_LOADED_TRANSITION_MS = 150;
+    public static final int SPRING_LOADED_EXIT_DELAY = 500;
+
     static WeakHashMap<Animator, Object> sAnimators = new WeakHashMap<Animator, Object>();
     static Animator.AnimatorListener sEndAnimListener = new Animator.AnimatorListener() {
         public void onAnimationStart(Animator animation) {
@@ -141,4 +150,31 @@
                     drawable.setAlpha(alpha);
                 }
             };
+
+    public static final Property<View, Float> SCALE_PROPERTY =
+            new Property<View, Float>(Float.class, "scale") {
+                @Override
+                public Float get(View view) {
+                    return view.getScaleX();
+                }
+
+                @Override
+                public void set(View view, Float scale) {
+                    view.setScaleX(scale);
+                    view.setScaleY(scale);
+                }
+            };
+
+    public static final Property<View, Float> ELEVATION =
+            new Property<View, Float>(Float.class, "elevation") {
+                @Override
+                public Float get(View view) {
+                    return view.getElevation();
+                }
+
+                @Override
+                public void set(View view, Float elevation) {
+                    view.setElevation(elevation);
+                }
+            };
 }
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 1ffe41b..0136c70 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -27,13 +27,10 @@
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.PackageInstallerCompat;
 import com.android.launcher3.compat.UserManagerCompat;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.dynamicui.ExtractionUtils;
 import com.android.launcher3.notification.NotificationListener;
 import com.android.launcher3.util.ConfigMonitor;
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.SettingsObserver;
-import com.android.launcher3.util.TestingUtils;
 
 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutionException;
@@ -42,8 +39,6 @@
 
 public class LauncherAppState {
 
-    public static final boolean PROFILE_STARTUP = FeatureFlags.IS_DOGFOOD_BUILD;
-
     // We do not need any synchronization for this variable as its only written on UI thread.
     private static LauncherAppState INSTANCE;
 
@@ -91,10 +86,6 @@
         Preconditions.assertUIThread();
         mContext = context;
 
-        if (TestingUtils.MEMORY_DUMP_ENABLED) {
-            TestingUtils.startTrackingMemory(mContext);
-        }
-
         mInvariantDeviceProfile = new InvariantDeviceProfile(mContext);
         mIconCache = new IconCache(mContext, mInvariantDeviceProfile);
         mWidgetCache = new WidgetPreviewLoader(mContext, mIconCache);
@@ -111,18 +102,11 @@
         filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
         filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
         filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNLOCKED);
-        // For extracting colors from the wallpaper
-        if (Utilities.ATLEAST_NOUGAT) {
-            // TODO: add a broadcast entry to the manifest for pre-N.
-            filter.addAction(Intent.ACTION_WALLPAPER_CHANGED);
-        }
 
         mContext.registerReceiver(mModel, filter);
         UserManagerCompat.getInstance(mContext).enableAndResetCache();
         new ConfigMonitor(mContext).register();
 
-        ExtractionUtils.startColorExtractionServiceIfNecessary(mContext);
-
         if (!mContext.getResources().getBoolean(R.bool.notification_badging_enabled)) {
             mNotificationBadgingObserver = null;
         } else {
diff --git a/src/com/android/launcher3/LauncherAppWidgetHost.java b/src/com/android/launcher3/LauncherAppWidgetHost.java
index 5573c5c..9aa74b3 100644
--- a/src/com/android/launcher3/LauncherAppWidgetHost.java
+++ b/src/com/android/launcher3/LauncherAppWidgetHost.java
@@ -16,7 +16,8 @@
 
 package com.android.launcher3;
 
-import android.app.Activity;
+import static android.app.Activity.RESULT_CANCELED;
+
 import android.appwidget.AppWidgetHost;
 import android.appwidget.AppWidgetHostView;
 import android.appwidget.AppWidgetManager;
@@ -41,12 +42,17 @@
  */
 public class LauncherAppWidgetHost extends AppWidgetHost {
 
+    private static final int FLAG_LISTENING = 1;
+    private static final int FLAG_RESUMED = 1 << 1;
+    private static final int FLAG_LISTEN_IF_RESUMED = 1 << 2;
+
     public static final int APPWIDGET_HOST_ID = 1024;
 
     private final ArrayList<ProviderChangedListener> mProviderChangeListeners = new ArrayList<>();
     private final SparseArray<LauncherAppWidgetHostView> mViews = new SparseArray<>();
 
     private final Context mContext;
+    private int mFlags = FLAG_RESUMED;
 
     public LauncherAppWidgetHost(Context context) {
         super(context, APPWIDGET_HOST_ID);
@@ -66,7 +72,7 @@
         if (FeatureFlags.GO_DISABLE_WIDGETS) {
             return;
         }
-
+        mFlags |= FLAG_LISTENING;
         try {
             super.startListening();
         } catch (Exception e) {
@@ -85,10 +91,59 @@
         if (FeatureFlags.GO_DISABLE_WIDGETS) {
             return;
         }
-
+        mFlags &= ~FLAG_LISTENING;
         super.stopListening();
     }
 
+    /**
+     * Updates the resumed state of the host.
+     * When a host is not resumed, it defers calls to startListening until host is resumed again.
+     * But if the host was already listening, it will not call stopListening.
+     *
+     * @see #setListenIfResumed(boolean)
+     */
+    public void setResumed(boolean isResumed) {
+        if (isResumed == ((mFlags & FLAG_RESUMED) != 0)) {
+            return;
+        }
+        if (isResumed) {
+            mFlags |= FLAG_RESUMED;
+            // Start listening if we were supposed to start listening on resume
+            if ((mFlags & FLAG_LISTEN_IF_RESUMED) != 0 && (mFlags & FLAG_LISTENING) == 0) {
+                startListening();
+            }
+        } else {
+            mFlags &= ~FLAG_RESUMED;
+        }
+    }
+
+    /**
+     * Updates the listening state of the host. If the host is not resumed, startListening is
+     * deferred until next resume.
+     *
+     * @see #setResumed(boolean)
+     */
+    public void setListenIfResumed(boolean listenIfResumed) {
+        if (!Utilities.ATLEAST_NOUGAT_MR1) {
+            return;
+        }
+        if (listenIfResumed == ((mFlags & FLAG_LISTEN_IF_RESUMED) != 0)) {
+            return;
+        }
+        if (listenIfResumed) {
+            mFlags |= FLAG_LISTEN_IF_RESUMED;
+            if ((mFlags & FLAG_RESUMED) != 0) {
+                // If we are resumed, start listening immediately. Note we do not check for
+                // duplicate calls before calling startListening as startListening is safe to call
+                // multiple times.
+                startListening();
+            }
+        } else {
+            mFlags &= ~FLAG_LISTEN_IF_RESUMED;
+            stopListening();
+        }
+    }
+
     @Override
     public int allocateAppWidgetId() {
         if (FeatureFlags.GO_DISABLE_WIDGETS) {
@@ -116,13 +171,12 @@
 
     public AppWidgetHostView createView(Context context, int appWidgetId,
             LauncherAppWidgetProviderInfo appWidget) {
-        if (appWidget.isCustomWidget) {
+        if (appWidget.isCustomWidget()) {
             LauncherAppWidgetHostView lahv = new LauncherAppWidgetHostView(context);
             LayoutInflater inflater = (LayoutInflater)
                     context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
             inflater.inflate(appWidget.initialLayout, lahv);
             lahv.setAppWidget(0, appWidget);
-            lahv.updateLastInflationOrientation();
             return lahv;
         } else {
             try {
@@ -204,12 +258,7 @@
     }
 
     private void sendActionCancelled(final BaseActivity activity, final int requestCode) {
-        new Handler().post(new Runnable() {
-            @Override
-            public void run() {
-                activity.onActivityResult(requestCode, Activity.RESULT_CANCELED, null);
-            }
-        });
+        new Handler().post(() -> activity.onActivityResult(requestCode, RESULT_CANCELED, null));
     }
 
     /**
diff --git a/src/com/android/launcher3/LauncherAppWidgetHostView.java b/src/com/android/launcher3/LauncherAppWidgetHostView.java
index 7fe1308..6f953e5 100644
--- a/src/com/android/launcher3/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/LauncherAppWidgetHostView.java
@@ -19,6 +19,7 @@
 import android.appwidget.AppWidgetHostView;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.Context;
+import android.content.res.Configuration;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.os.Handler;
@@ -58,10 +59,14 @@
 
     private final CheckLongPressHelper mLongPressHelper;
     private final StylusEventHelper mStylusEventHelper;
-    private final Context mContext;
+    private final Launcher mLauncher;
+
+    private static final int DONT_REINFLATE = 0;
+    private static final int REINFLATE_ON_RESUME = 1;
+    private static final int REINFLATE_ON_CONFIG_CHANGE = 2;
 
     @ViewDebug.ExportedProperty(category = "launcher")
-    private int mPreviousOrientation;
+    private int mReinflateStatus;
 
     private float mSlop;
 
@@ -85,11 +90,11 @@
 
     public LauncherAppWidgetHostView(Context context) {
         super(context);
-        mContext = context;
+        mLauncher = Launcher.getLauncher(context);
         mLongPressHelper = new CheckLongPressHelper(this, this);
         mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this);
         mInflater = LayoutInflater.from(context);
-        setAccessibilityDelegate(Launcher.getLauncher(context).getAccessibilityDelegate());
+        setAccessibilityDelegate(mLauncher.getAccessibilityDelegate());
         setBackgroundResource(R.drawable.widget_internal_focus_bg);
 
         if (Utilities.ATLEAST_OREO) {
@@ -112,18 +117,28 @@
         return mInflater.inflate(R.layout.appwidget_error, this, false);
     }
 
-    public void updateLastInflationOrientation() {
-        mPreviousOrientation = mContext.getResources().getConfiguration().orientation;
-    }
-
     @Override
     public void updateAppWidget(RemoteViews remoteViews) {
-        // Store the orientation in which the widget was inflated
-        updateLastInflationOrientation();
         super.updateAppWidget(remoteViews);
 
         // The provider info or the views might have changed.
         checkIfAutoAdvance();
+
+        // It is possible that widgets can receive updates while launcher is not in the foreground.
+        // Consequently, the widgets will be inflated for the orientation of the foreground activity
+        // (framework issue). On resuming, we ensure that any widgets are inflated for the current
+        // orientation.
+        if (mReinflateStatus == DONT_REINFLATE && !isSameOrientation()) {
+            mReinflateStatus = REINFLATE_ON_RESUME;
+            if (!mLauncher.waitUntilResume(new ReInflateRunnable())) {
+                mReinflateStatus = REINFLATE_ON_CONFIG_CHANGE;
+            }
+        }
+    }
+
+    private boolean isSameOrientation() {
+        return mLauncher.getResources().getConfiguration().orientation ==
+                mLauncher.getOrientation();
     }
 
     private boolean checkScrollableRecursively(ViewGroup viewGroup) {
@@ -142,14 +157,6 @@
         return false;
     }
 
-    public boolean isReinflateRequired(int orientation) {
-        // Re-inflate is required if the orientation has changed since last inflated.
-        if (mPreviousOrientation != orientation) {
-           return true;
-       }
-       return false;
-    }
-
     public boolean onInterceptTouchEvent(MotionEvent ev) {
         // Just in case the previous long press hasn't been cleared, we make sure to start fresh
         // on touch down.
@@ -473,4 +480,45 @@
     public PointF getTranslationForCentering() {
         return mTranslationForCentering;
     }
+
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+
+        if (mReinflateStatus == REINFLATE_ON_CONFIG_CHANGE) {
+            // We are finally in the same orientation
+            reinflateIfNecessary();
+        }
+    }
+
+    private void reinflateIfNecessary() {
+        if (!isSameOrientation()) {
+            // We cannot reinflate yet, wait until next config change
+            mReinflateStatus = REINFLATE_ON_CONFIG_CHANGE;
+            return;
+        }
+
+        mReinflateStatus = DONT_REINFLATE;
+        if (isAttachedToWindow()) {
+            LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) getTag();
+            reinflate();
+        }
+    }
+
+    public void reinflate() {
+        LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) getTag();
+        // Remove and rebind the current widget (which was inflated in the wrong
+        // orientation), but don't delete it from the database
+        mLauncher.removeItem(this, info, false  /* deleteFromDb */);
+        mLauncher.bindAppWidget(info);
+    }
+
+    private class ReInflateRunnable implements Runnable {
+        @Override
+        public void run() {
+            if (mReinflateStatus == REINFLATE_ON_RESUME) {
+                reinflateIfNecessary();
+            }
+        }
+    }
 }
diff --git a/src/com/android/launcher3/LauncherAppWidgetInfo.java b/src/com/android/launcher3/LauncherAppWidgetInfo.java
index 6f23e56..051846c 100644
--- a/src/com/android/launcher3/LauncherAppWidgetInfo.java
+++ b/src/com/android/launcher3/LauncherAppWidgetInfo.java
@@ -71,7 +71,7 @@
     /**
      * Indicates that this is a locally defined widget and hence has no system allocated id.
      */
-    static final int CUSTOM_WIDGET_ID = -100;
+    public static final int CUSTOM_WIDGET_ID = -100;
 
     /**
      * Identifier for this widget when talking with
@@ -104,15 +104,15 @@
     private boolean mHasNotifiedInitialWidgetSizeChanged;
 
     public LauncherAppWidgetInfo(int appWidgetId, ComponentName providerName) {
-        if (appWidgetId == CUSTOM_WIDGET_ID) {
+        this.appWidgetId = appWidgetId;
+        this.providerName = providerName;
+
+        if (isCustomWidget()) {
             itemType = LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
         } else {
             itemType = LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
         }
 
-        this.appWidgetId = appWidgetId;
-        this.providerName = providerName;
-
         // Since the widget isn't instantiated yet, we don't know these values. Set them to -1
         // to indicate that they should be calculated based on the layout and minWidth/minHeight
         spanX = -1;
@@ -128,7 +128,7 @@
     }
 
     public boolean isCustomWidget() {
-        return appWidgetId == CUSTOM_WIDGET_ID;
+        return appWidgetId <= CUSTOM_WIDGET_ID;
     }
 
     @Override
diff --git a/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java b/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
index 6cb703b..c713992 100644
--- a/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
+++ b/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
@@ -2,15 +2,11 @@
 
 import android.appwidget.AppWidgetHostView;
 import android.appwidget.AppWidgetProviderInfo;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.graphics.Point;
 import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
 import android.os.Parcel;
-import android.os.Process;
-import android.os.UserHandle;
 
 /**
  * This class is a thin wrapper around the framework AppWidgetProviderInfo class. This class affords
@@ -20,7 +16,7 @@
  */
 public class LauncherAppWidgetProviderInfo extends AppWidgetProviderInfo {
 
-    public boolean isCustomWidget = false;
+    public static final String CLS_CUSTOM_WIDGET_PREFIX = "#custom-widget-";
 
     public int spanX;
     public int spanY;
@@ -48,22 +44,12 @@
         return launcherInfo;
     }
 
-    private LauncherAppWidgetProviderInfo(Parcel in) {
+    protected LauncherAppWidgetProviderInfo() {}
+
+    protected LauncherAppWidgetProviderInfo(Parcel in) {
         super(in);
     }
 
-    public LauncherAppWidgetProviderInfo(Context context, CustomAppWidget widget) {
-        isCustomWidget = true;
-
-        provider = new ComponentName(context, widget.getClass().getName());
-        icon = widget.getIcon();
-        label = widget.getLabel();
-        previewImage = widget.getPreviewImage();
-        initialLayout = widget.getWidgetLayout();
-        resizeMode = widget.getResizeMode();
-        initSpans(context);
-    }
-
     public void initSpans(Context context) {
         InvariantDeviceProfile idp = LauncherAppState.getIDP(context);
 
@@ -97,34 +83,15 @@
     }
 
     public String getLabel(PackageManager packageManager) {
-        if (isCustomWidget) {
-            return Utilities.trim(label);
-        }
         return super.loadLabel(packageManager);
     }
 
-    public Drawable getIcon(Context context, IconCache cache) {
-        if (isCustomWidget) {
-            return cache.getFullResIcon(provider.getPackageName(), icon);
-        }
-        return super.loadIcon(context, LauncherAppState.getIDP(context).fillResIconDpi);
+    public Point getMinSpans() {
+        return new Point((resizeMode & RESIZE_HORIZONTAL) != 0 ? minSpanX : -1,
+                (resizeMode & RESIZE_VERTICAL) != 0 ? minSpanY : -1);
     }
 
-    public String toString(PackageManager pm) {
-        if (isCustomWidget) {
-            return "WidgetProviderInfo(" + provider + ")";
-        }
-        return String.format("WidgetProviderInfo provider:%s package:%s short:%s label:%s",
-                provider.toString(), provider.getPackageName(), provider.getShortClassName(), getLabel(pm));
-    }
-
-    public Point getMinSpans(InvariantDeviceProfile idp, Context context) {
-        return new Point(
-                (resizeMode & RESIZE_HORIZONTAL) != 0 ? minSpanX : -1,
-                        (resizeMode & RESIZE_VERTICAL) != 0 ? minSpanY : -1);
-    }
-
-    public UserHandle getUser() {
-        return isCustomWidget ? Process.myUserHandle() : getProfile();
+    public boolean isCustomWidget() {
+        return provider.getClassName().startsWith(CLS_CUSTOM_WIDGET_PREFIX);
     }
  }
diff --git a/src/com/android/launcher3/LauncherCallbacks.java b/src/com/android/launcher3/LauncherCallbacks.java
index 66da046..928258f 100644
--- a/src/com/android/launcher3/LauncherCallbacks.java
+++ b/src/com/android/launcher3/LauncherCallbacks.java
@@ -19,14 +19,10 @@
 import android.content.Intent;
 import android.os.Bundle;
 import android.view.Menu;
-import android.view.View;
-
-import com.android.launcher3.util.ComponentKeyMapper;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.List;
 
 /**
  * LauncherCallbacks is an interface used to extend the Launcher activity. It includes many hooks
@@ -43,15 +39,12 @@
      */
     void preOnCreate();
     void onCreate(Bundle savedInstanceState);
-    void preOnResume();
     void onResume();
     void onStart();
     void onStop();
     void onPause();
     void onDestroy();
     void onSaveInstanceState(Bundle outState);
-    void onPostCreate(Bundle savedInstanceState);
-    void onNewIntent(Intent intent);
     void onActivityResult(int requestCode, int resultCode, Intent data);
     void onRequestPermissionsResult(int requestCode, String[] permissions,
             int[] grantResults);
@@ -68,31 +61,16 @@
      * Extension points for providing custom behavior on certain user interactions.
      */
     void onLauncherProviderChange();
-    void finishBindingItems(final boolean upgradePath);
     void bindAllApplications(ArrayList<AppInfo> apps);
-    void onInteractionBegin();
-    void onInteractionEnd();
-
-    @Deprecated
-    void onWorkspaceLockedChanged();
 
     /**
      * Starts a search with {@param initialQuery}. Return false if search was not started.
      */
     boolean startSearch(
             String initialQuery, boolean selectInitialQuery, Bundle appSearchData);
-    boolean hasCustomContentToLeft();
-    void populateCustomContentContainer();
-    View getQsbBar();
-    Bundle getAdditionalSearchWidgetOptions();
 
     /*
      * Extensions points for adding / replacing some other aspects of the Launcher experience.
      */
-    boolean shouldMoveToDefaultScreenOnHomeIntent();
     boolean hasSettings();
-    List<ComponentKeyMapper<AppInfo>> getPredictedApps();
-    int SEARCH_BAR_HEIGHT_NORMAL = 0, SEARCH_BAR_HEIGHT_TALL = 1;
-    /** Must return one of {@link #SEARCH_BAR_HEIGHT_NORMAL} or {@link #SEARCH_BAR_HEIGHT_TALL} */
-    int getSearchBarHeight();
 }
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index a906b00..469c5f1 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -37,7 +37,6 @@
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
 import com.android.launcher3.compat.UserManagerCompat;
-import com.android.launcher3.dynamicui.ExtractionUtils;
 import com.android.launcher3.graphics.LauncherIcons;
 import com.android.launcher3.model.AddWorkspaceItemsTask;
 import com.android.launcher3.model.BgDataModel;
@@ -63,6 +62,7 @@
 import com.android.launcher3.util.Provider;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.util.ViewOnDrawExecutor;
+import com.android.launcher3.widget.WidgetListRowEntry;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -134,7 +134,7 @@
         }
     };
 
-    public interface Callbacks extends LauncherAppWidgetHost.ProviderChangedListener {
+    public interface Callbacks {
         public boolean setLoadOnResume();
         public int getCurrentWorkspaceScreen();
         public void clearPendingBinds();
@@ -154,7 +154,7 @@
         public void bindRestoreItemsChange(HashSet<ItemInfo> updates);
         public void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher);
         public void bindAppInfosRemoved(ArrayList<AppInfo> appInfos);
-        public void bindAllWidgets(MultiHashMap<PackageItemInfo, WidgetItem> widgets);
+        public void bindAllWidgets(ArrayList<WidgetListRowEntry> widgets);
         public void onPageBoundSynchronously(int page);
         public void executeOnNextDraw(ViewOnDrawExecutor executor);
         public void bindDeepShortcutMap(MultiHashMap<ComponentKey, String> deepShortcutMap);
@@ -193,9 +193,8 @@
     /**
      * Adds the provided items to the workspace.
      */
-    public void addAndBindAddedWorkspaceItems(
-            Provider<List<Pair<ItemInfo, Object>>> appsProvider) {
-        enqueueModelUpdateTask(new AddWorkspaceItemsTask(appsProvider));
+    public void addAndBindAddedWorkspaceItems(List<Pair<ItemInfo, Object>> itemList) {
+        enqueueModelUpdateTask(new AddWorkspaceItemsTask(itemList));
     }
 
     public ModelWriter getWriter(boolean hasVerticalHotseat) {
@@ -406,8 +405,6 @@
                     enqueueModelUpdateTask(new UserLockStateChangedTask(user));
                 }
             }
-        } else if (Intent.ACTION_WALLPAPER_CHANGED.equals(action)) {
-            ExtractionUtils.startColorExtractionServiceIfNecessary(context);
         }
     }
 
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index b31df98..98568e4 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -44,7 +44,6 @@
 import android.os.Handler;
 import android.os.Message;
 import android.os.Process;
-import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.text.TextUtils;
@@ -55,7 +54,6 @@
 import com.android.launcher3.LauncherSettings.WorkspaceScreens;
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.dynamicui.ExtractionUtils;
 import com.android.launcher3.graphics.IconShapeOverride;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.model.DbDowngradeHelper;
@@ -87,7 +85,7 @@
      */
     public static final int SCHEMA_VERSION = 27;
 
-    public static final String AUTHORITY = (BuildConfig.APPLICATION_ID + ".settings").intern();
+    public static final String AUTHORITY = FeatureFlags.AUTHORITY;
 
     static final String EMPTY_DATABASE_CREATED = "EMPTY_DATABASE_CREATED";
 
@@ -149,9 +147,6 @@
      */
     protected synchronized void createDbIfNotExists() {
         if (mOpenHelper == null) {
-            if (LauncherAppState.PROFILE_STARTUP) {
-                Trace.beginSection("Opening workspace DB");
-            }
             mOpenHelper = new DatabaseHelper(getContext(), mListenerHandler);
 
             if (RestoreDbTask.isPending(getContext())) {
@@ -162,10 +157,6 @@
                 // executed again.
                 RestoreDbTask.setPending(getContext(), false);
             }
-
-            if (LauncherAppState.PROFILE_STARTUP) {
-                Trace.endSection();
-            }
         }
     }
 
@@ -372,19 +363,6 @@
         createDbIfNotExists();
 
         switch (method) {
-            case LauncherSettings.Settings.METHOD_SET_EXTRACTED_COLORS_AND_WALLPAPER_ID: {
-                String extractedColors = extras.getString(
-                        LauncherSettings.Settings.EXTRA_EXTRACTED_COLORS);
-                int wallpaperId = extras.getInt(LauncherSettings.Settings.EXTRA_WALLPAPER_ID);
-                Utilities.getPrefs(getContext()).edit()
-                        .putString(ExtractionUtils.EXTRACTED_COLORS_PREFERENCE_KEY, extractedColors)
-                        .putInt(ExtractionUtils.WALLPAPER_ID_PREFERENCE_KEY, wallpaperId)
-                        .apply();
-                mListenerHandler.sendEmptyMessage(ChangeListenerWrapper.MSG_EXTRACTED_COLORS_CHANGED);
-                Bundle result = new Bundle();
-                result.putString(LauncherSettings.Settings.EXTRA_VALUE, extractedColors);
-                return result;
-            }
             case LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG: {
                 clearFlagEmptyDbCreated();
                 return null;
@@ -1160,8 +1138,7 @@
     private static class ChangeListenerWrapper implements Handler.Callback {
 
         private static final int MSG_LAUNCHER_PROVIDER_CHANGED = 1;
-        private static final int MSG_EXTRACTED_COLORS_CHANGED = 2;
-        private static final int MSG_APP_WIDGET_HOST_RESET = 3;
+        private static final int MSG_APP_WIDGET_HOST_RESET = 2;
 
         private LauncherProviderChangeListener mListener;
 
@@ -1172,9 +1149,6 @@
                     case MSG_LAUNCHER_PROVIDER_CHANGED:
                         mListener.onLauncherProviderChanged();
                         break;
-                    case MSG_EXTRACTED_COLORS_CHANGED:
-                        mListener.onExtractedColorsChanged();
-                        break;
                     case MSG_APP_WIDGET_HOST_RESET:
                         mListener.onAppWidgetHostReset();
                         break;
diff --git a/src/com/android/launcher3/LauncherProviderChangeListener.java b/src/com/android/launcher3/LauncherProviderChangeListener.java
index 7044812..0243088 100644
--- a/src/com/android/launcher3/LauncherProviderChangeListener.java
+++ b/src/com/android/launcher3/LauncherProviderChangeListener.java
@@ -9,7 +9,5 @@
 
     void onLauncherProviderChanged();
 
-    void onExtractedColorsChanged();
-
     void onAppWidgetHostReset();
 }
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index 87f62eb..3b337ef 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -304,11 +304,6 @@
 
         public static final String METHOD_LOAD_DEFAULT_FAVORITES = "load_default_favorites";
 
-        public static final String METHOD_SET_EXTRACTED_COLORS_AND_WALLPAPER_ID =
-                "set_extracted_colors_and_wallpaper_id_setting";
-        public static final String EXTRA_EXTRACTED_COLORS = "extra_extractedColors";
-        public static final String EXTRA_WALLPAPER_ID = "extra_wallpaperId";
-
         public static final String METHOD_REMOVE_GHOST_WIDGETS = "remove_ghost_widgets";
 
         public static final String EXTRA_VALUE = "value";
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
new file mode 100644
index 0000000..d6cd8a3
--- /dev/null
+++ b/src/com/android/launcher3/LauncherState.java
@@ -0,0 +1,134 @@
+/*
+ * 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;
+
+import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_AUTO;
+import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
+import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
+
+import android.view.View;
+
+import com.android.launcher3.states.AllAppsState;
+import com.android.launcher3.states.SpringLoadedState;
+import com.android.launcher3.uioverrides.OverviewState;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+
+import java.util.Arrays;
+
+
+/**
+ * Base state for various states used for the Launcher
+ */
+public class LauncherState {
+
+    protected static final int FLAG_SHOW_SCRIM = 1 << 0;
+    protected static final int FLAG_MULTI_PAGE = 1 << 1;
+    protected static final int FLAG_DISABLE_ACCESSIBILITY = 1 << 2;
+    protected static final int FLAG_DO_NOT_RESTORE = 1 << 3;
+
+    private static final LauncherState[] sAllStates = new LauncherState[4];
+
+    public static final LauncherState NORMAL = new LauncherState(0, ContainerType.WORKSPACE,
+            0, 1f, FLAG_DO_NOT_RESTORE);
+
+    public static final LauncherState ALL_APPS = new AllAppsState(1);
+
+    public static final LauncherState SPRING_LOADED = new SpringLoadedState(2);
+
+    public static final LauncherState OVERVIEW = new OverviewState(3);
+
+    public final int ordinal;
+
+    /**
+     * Used for containerType in {@link com.android.launcher3.logging.UserEventDispatcher}
+     */
+    public final int containerType;
+
+    /**
+     * True if the state can be persisted across activity restarts.
+     */
+    public final boolean doNotRestore;
+
+    /**
+     * True if workspace has multiple pages visible.
+     */
+    public final boolean hasMultipleVisiblePages;
+
+    /**
+     * Accessibility flag for workspace and its pages.
+     * @see android.view.View#setImportantForAccessibility(int)
+     */
+    public final int workspaceAccessibilityFlag;
+
+    /**
+     * Properties related to state transition animation
+     *
+     * @see WorkspaceStateTransitionAnimation
+     */
+    public final boolean hasScrim;
+    public final int transitionDuration;
+
+    /**
+     * Fraction shift in the vertical translation UI and related properties
+     *
+     * @see com.android.launcher3.allapps.AllAppsTransitionController
+     */
+    public final float verticalProgress;
+
+    public LauncherState(int id, int containerType, int transitionDuration, float verticalProgress,
+            int flags) {
+        this.containerType = containerType;
+        this.transitionDuration = transitionDuration;
+
+        this.hasScrim = (flags & FLAG_SHOW_SCRIM) != 0;
+        this.hasMultipleVisiblePages = (flags & FLAG_MULTI_PAGE) != 0;
+        this.workspaceAccessibilityFlag = (flags & FLAG_DISABLE_ACCESSIBILITY) != 0
+                ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+                : IMPORTANT_FOR_ACCESSIBILITY_AUTO;
+        this.doNotRestore = (flags & FLAG_DO_NOT_RESTORE) != 0;
+
+        this.verticalProgress = verticalProgress;
+
+        this.ordinal = id;
+        sAllStates[id] = this;
+    }
+
+    public static LauncherState[] values() {
+        return Arrays.copyOf(sAllStates, sAllStates.length);
+    }
+
+    public float[] getWorkspaceScaleAndTranslation(Launcher launcher) {
+        return new float[] {1, 0};
+    }
+
+    public void onStateEnabled(Launcher launcher) {
+        dispatchWindowStateChanged(launcher);
+    }
+
+    public void onStateDisabled(Launcher launcher) { }
+
+    public View getFinalFocus(Launcher launcher) {
+        return launcher.getWorkspace();
+    }
+
+    public String getDescription(Launcher launcher) {
+        return launcher.getWorkspace().getCurrentPageDescription();
+    }
+
+    protected static void dispatchWindowStateChanged(Launcher launcher) {
+        launcher.getWindow().getDecorView().sendAccessibilityEvent(TYPE_WINDOW_STATE_CHANGED);
+    }
+}
diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/LauncherStateManager.java
new file mode 100644
index 0000000..1e6016b
--- /dev/null
+++ b/src/com/android/launcher3/LauncherStateManager.java
@@ -0,0 +1,338 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import static com.android.launcher3.LauncherState.NORMAL;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.View;
+
+import com.android.launcher3.anim.AnimationLayerSet;
+import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.uioverrides.UiFactory;
+
+/**
+ * TODO: figure out what kind of tests we can write for this
+ *
+ * Things to test when changing the following class.
+ *   - Home from workspace
+ *          - from center screen
+ *          - from other screens
+ *   - Home from all apps
+ *          - from center screen
+ *          - from other screens
+ *   - Back from all apps
+ *          - from center screen
+ *          - from other screens
+ *   - Launch app from workspace and quit
+ *          - with back
+ *          - with home
+ *   - Launch app from all apps and quit
+ *          - with back
+ *          - with home
+ *   - Go to a screen that's not the default, then all
+ *     apps, and launch and app, and go back
+ *          - with back
+ *          -with home
+ *   - On workspace, long press power and go back
+ *          - with back
+ *          - with home
+ *   - On all apps, long press power and go back
+ *          - with back
+ *          - with home
+ *   - On workspace, power off
+ *   - On all apps, power off
+ *   - Launch an app and turn off the screen while in that app
+ *          - Go back with home key
+ *          - Go back with back key  TODO: make this not go to workspace
+ *          - From all apps
+ *          - From workspace
+ *   - Enter and exit car mode (becuase it causes an extra configuration changed)
+ *          - From all apps
+ *          - From the center workspace
+ *          - From another workspace
+ */
+public class LauncherStateManager {
+
+    public static final String TAG = "StateManager";
+
+    private final AnimationConfig mConfig = new AnimationConfig();
+    private final Handler mUiHandler;
+    private final Launcher mLauncher;
+
+    private StateHandler[] mStateHandlers;
+    private LauncherState mState = NORMAL;
+
+    private StateListener mStateListener;
+
+    public LauncherStateManager(Launcher l) {
+        mUiHandler = new Handler(Looper.getMainLooper());
+        mLauncher = l;
+    }
+
+    public LauncherState getState() {
+        return mState;
+    }
+
+    private StateHandler[] getStateHandlers() {
+        if (mStateHandlers == null) {
+            mStateHandlers = UiFactory.getStateHandler(mLauncher);
+        }
+        return mStateHandlers;
+    }
+
+    public void setStateListener(StateListener stateListener) {
+        mStateListener = stateListener;
+    }
+
+    /**
+     * @see #goToState(LauncherState, boolean, Runnable)
+     */
+    public void goToState(LauncherState state) {
+        goToState(state, true, 0, null);
+    }
+
+    /**
+     * @see #goToState(LauncherState, boolean, Runnable)
+     */
+    public void goToState(LauncherState state, boolean animated) {
+        goToState(state, animated, 0, null);
+    }
+
+    /**
+     * Changes the Launcher state to the provided state.
+     *
+     * @param animated false if the state should change immediately without any animation,
+     *                true otherwise
+     * @paras onCompleteRunnable any action to perform at the end of the transition, of null.
+     */
+    public void goToState(LauncherState state, boolean animated, Runnable onCompleteRunnable) {
+        goToState(state, animated, 0, onCompleteRunnable);
+    }
+
+    /**
+     * Changes the Launcher state to the provided state after the given delay.
+     */
+    public void goToState(LauncherState state, long delay, Runnable onCompleteRunnable) {
+        goToState(state, true, delay, onCompleteRunnable);
+    }
+
+    /**
+     * Changes the Launcher state to the provided state after the given delay.
+     */
+    public void goToState(LauncherState state, long delay) {
+        goToState(state, true, delay, null);
+    }
+
+    private void goToState(LauncherState state, boolean animated, long delay,
+            Runnable onCompleteRunnable) {
+        if (mLauncher.isInState(state) && mConfig.mCurrentAnimation == null) {
+
+            // Run any queued runnable
+            if (onCompleteRunnable != null) {
+                onCompleteRunnable.run();
+            }
+            return;
+        }
+
+        // Cancel the current animation
+        mConfig.reset();
+
+        if (!animated) {
+            setState(state);
+            for (StateHandler handler : getStateHandlers()) {
+                handler.setState(state);
+            }
+            if (mStateListener != null) {
+                mStateListener.onStateSetImmediately(state);
+            }
+            mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
+
+            // Run any queued runnable
+            if (onCompleteRunnable != null) {
+                onCompleteRunnable.run();
+            }
+            return;
+        }
+
+        // Since state NORMAL can be reached from multiple states, just assume that the
+        // transition plays in reverse and use the same duration as previous state.
+        mConfig.duration = state == NORMAL ? mState.transitionDuration : state.transitionDuration;
+
+        AnimatorSet animation = createAnimationToNewWorkspaceInternal(state, onCompleteRunnable);
+        Runnable runnable = new StartAnimRunnable(animation, state.getFinalFocus(mLauncher));
+        if (delay > 0) {
+            mUiHandler.postDelayed(runnable, delay);
+        } else {
+            mUiHandler.post(runnable);
+        }
+    }
+
+    /**
+     * Creates a {@link AnimatorPlaybackController} that can be used for a controlled
+     * state transition.
+     * @param state the final state for the transition.
+     * @param duration intended duration for normal playback. Use higher duration for better
+     *                accuracy.
+     */
+    public AnimatorPlaybackController createAnimationToNewWorkspace(
+            LauncherState state, long duration) {
+        mConfig.reset();
+        mConfig.userControlled = true;
+        mConfig.duration = duration;
+        return AnimatorPlaybackController.wrap(
+                createAnimationToNewWorkspaceInternal(state, null), duration);
+    }
+
+    protected AnimatorSet createAnimationToNewWorkspaceInternal(final LauncherState state,
+            final Runnable onCompleteRunnable) {
+        final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
+        final AnimationLayerSet layerViews = new AnimationLayerSet();
+
+        for (StateHandler handler : getStateHandlers()) {
+            handler.setStateWithAnimation(state, layerViews, animation, mConfig);
+        }
+        animation.addListener(layerViews);
+        animation.addListener(new AnimationSuccessListener() {
+
+            @Override
+            public void onAnimationStart(Animator animation) {
+                // Change the internal state only when the transition actually starts
+                setState(state);
+                if (mStateListener != null) {
+                    mStateListener.onStateTransitionStart(state);
+                }
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                super.onAnimationEnd(animation);
+                if (mStateListener != null) {
+                    mStateListener.onStateTransitionComplete(mState);
+                }
+            }
+
+            @Override
+            public void onAnimationSuccess(Animator animator) {
+                // Run any queued runnables
+                if (onCompleteRunnable != null) {
+                    onCompleteRunnable.run();
+                }
+
+                mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
+            }
+        });
+        mConfig.setAnimation(animation);
+        return mConfig.mCurrentAnimation;
+    }
+
+    private void setState(LauncherState state) {
+        mState.onStateDisabled(mLauncher);
+        mState = state;
+        mState.onStateEnabled(mLauncher);
+        mLauncher.getAppWidgetHost().setResumed(state == LauncherState.NORMAL);
+    }
+
+    /**
+     * Cancels the current animation.
+     */
+    public void cancelAnimation() {
+        mConfig.reset();
+    }
+
+    private class StartAnimRunnable implements Runnable {
+
+        private final AnimatorSet mAnim;
+        private final View mViewToFocus;
+
+        public StartAnimRunnable(AnimatorSet anim, View viewToFocus) {
+            mAnim = anim;
+            mViewToFocus = viewToFocus;
+        }
+
+        @Override
+        public void run() {
+            if (mConfig.mCurrentAnimation != mAnim) {
+                return;
+            }
+            if (mViewToFocus != null) {
+                mViewToFocus.requestFocus();
+            }
+            mAnim.start();
+        }
+    }
+
+    public static class AnimationConfig extends AnimatorListenerAdapter {
+        public long duration;
+        public boolean userControlled;
+
+        private AnimatorSet mCurrentAnimation;
+
+        public void reset() {
+            duration = 0;
+            userControlled = false;
+
+            if (mCurrentAnimation != null) {
+                mCurrentAnimation.setDuration(0);
+                mCurrentAnimation.cancel();
+                mCurrentAnimation = null;
+            }
+        }
+
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            if (mCurrentAnimation == animation) {
+                mCurrentAnimation = null;
+            }
+        }
+
+        public void setAnimation(AnimatorSet animation) {
+            mCurrentAnimation = animation;
+            mCurrentAnimation.addListener(this);
+        }
+    }
+
+    public interface StateHandler {
+
+        /**
+         * Updates the UI to {@param state} without any animations
+         */
+        void setState(LauncherState state);
+
+        /**
+         * Sets the UI to {@param state} by animating any changes.
+         */
+        void setStateWithAnimation(LauncherState toState, AnimationLayerSet layerViews,
+                AnimatorSet anim, AnimationConfig config);
+    }
+
+    public interface StateListener {
+
+        /**
+         * Called when the state is set without an animation.
+         */
+        void onStateSetImmediately(LauncherState state);
+
+        void onStateTransitionStart(LauncherState toState);
+        void onStateTransitionComplete(LauncherState finalState);
+    }
+}
diff --git a/src/com/android/launcher3/LauncherStateTransitionAnimation.java b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
deleted file mode 100644
index e247490..0000000
--- a/src/com/android/launcher3/LauncherStateTransitionAnimation.java
+++ /dev/null
@@ -1,719 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.animation.PropertyValuesHolder;
-import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
-import android.content.res.Resources;
-import android.util.Log;
-import android.view.View;
-import android.view.animation.AccelerateInterpolator;
-
-import com.android.launcher3.allapps.AllAppsContainerView;
-import com.android.launcher3.allapps.AllAppsTransitionController;
-import com.android.launcher3.anim.AnimationLayerSet;
-import com.android.launcher3.anim.CircleRevealOutlineProvider;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.util.Thunk;
-import com.android.launcher3.widget.WidgetsContainerView;
-
-/**
- * TODO: figure out what kind of tests we can write for this
- *
- * Things to test when changing the following class.
- *   - Home from workspace
- *          - from center screen
- *          - from other screens
- *   - Home from all apps
- *          - from center screen
- *          - from other screens
- *   - Back from all apps
- *          - from center screen
- *          - from other screens
- *   - Launch app from workspace and quit
- *          - with back
- *          - with home
- *   - Launch app from all apps and quit
- *          - with back
- *          - with home
- *   - Go to a screen that's not the default, then all
- *     apps, and launch and app, and go back
- *          - with back
- *          -with home
- *   - On workspace, long press power and go back
- *          - with back
- *          - with home
- *   - On all apps, long press power and go back
- *          - with back
- *          - with home
- *   - On workspace, power off
- *   - On all apps, power off
- *   - Launch an app and turn off the screen while in that app
- *          - Go back with home key
- *          - Go back with back key  TODO: make this not go to workspace
- *          - From all apps
- *          - From workspace
- *   - Enter and exit car mode (becuase it causes an extra configuration changed)
- *          - From all apps
- *          - From the center workspace
- *          - From another workspace
- */
-public class LauncherStateTransitionAnimation {
-
-    /**
-     * animation used for the widget tray
-     */
-    public static final int CIRCULAR_REVEAL = 0;
-    /**
-     * animation used for all apps tray
-     */
-    public static final int PULLUP = 1;
-
-    private static final float FINAL_REVEAL_ALPHA_FOR_WIDGETS = 0.3f;
-
-    /**
-     * Private callbacks made during transition setup.
-     */
-    private static class PrivateTransitionCallbacks {
-        private final float materialRevealViewFinalAlpha;
-
-        PrivateTransitionCallbacks(float revealAlpha) {
-            materialRevealViewFinalAlpha = revealAlpha;
-        }
-
-        float getMaterialRevealViewStartFinalRadius() {
-            return 0;
-        }
-        AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(View revealView,
-                View buttonView) {
-            return null;
-        }
-        void onTransitionComplete() {}
-    }
-
-    public static final String TAG = "LSTAnimation";
-
-    public static final int SINGLE_FRAME_DELAY = 16;
-
-    @Thunk Launcher mLauncher;
-    @Thunk AnimatorSet mCurrentAnimation;
-    AllAppsTransitionController mAllAppsController;
-
-    public LauncherStateTransitionAnimation(Launcher l, AllAppsTransitionController allAppsController) {
-        mLauncher = l;
-        mAllAppsController = allAppsController;
-    }
-
-    /**
-     * Starts an animation to the apps view.
-     */
-    public void startAnimationToAllApps(final boolean animated) {
-        final AllAppsContainerView toView = mLauncher.getAppsView();
-        final View buttonView = mLauncher.getStartViewForAllAppsRevealAnimation();
-        PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks(1f) {
-            @Override
-            public float getMaterialRevealViewStartFinalRadius() {
-                int allAppsButtonSize = mLauncher.getDeviceProfile().allAppsButtonVisualSize;
-                return allAppsButtonSize / 2;
-            }
-            @Override
-            public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(
-                    final View revealView, final View allAppsButtonView) {
-                return new AnimatorListenerAdapter() {
-                    public void onAnimationStart(Animator animation) {
-                        allAppsButtonView.setVisibility(View.INVISIBLE);
-                    }
-                    public void onAnimationEnd(Animator animation) {
-                        allAppsButtonView.setVisibility(View.VISIBLE);
-                    }
-                };
-            }
-            @Override
-            void onTransitionComplete() {
-                mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
-            }
-        };
-        // Only animate the search bar if animating from spring loaded mode back to all apps
-        startAnimationToOverlay(
-                Workspace.State.NORMAL_HIDDEN, buttonView, toView, animated, PULLUP, cb);
-    }
-
-    /**
-     * Starts an animation to the widgets view.
-     */
-    public void startAnimationToWidgets(final boolean animated) {
-        final WidgetsContainerView toView = mLauncher.getWidgetsView();
-        final View buttonView = mLauncher.getWidgetsButton();
-        startAnimationToOverlay(
-                Workspace.State.OVERVIEW_HIDDEN, buttonView, toView, animated, CIRCULAR_REVEAL,
-                new PrivateTransitionCallbacks(FINAL_REVEAL_ALPHA_FOR_WIDGETS){
-                    @Override
-                    void onTransitionComplete() {
-                        mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
-                    }
-                });
-    }
-
-    /**
-     * Starts an animation to the workspace from the current overlay view.
-     */
-    public void startAnimationToWorkspace(final Launcher.State fromState,
-            final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState,
-            final boolean animated, final Runnable onCompleteRunnable) {
-        if (toWorkspaceState != Workspace.State.NORMAL &&
-                toWorkspaceState != Workspace.State.SPRING_LOADED &&
-                toWorkspaceState != Workspace.State.OVERVIEW) {
-            Log.e(TAG, "Unexpected call to startAnimationToWorkspace");
-        }
-
-        if (fromState == Launcher.State.APPS || fromState == Launcher.State.APPS_SPRING_LOADED
-                || mAllAppsController.isTransitioning()) {
-            startAnimationToWorkspaceFromAllApps(fromWorkspaceState, toWorkspaceState,
-                    animated, PULLUP, onCompleteRunnable);
-        } else if (fromState == Launcher.State.WIDGETS ||
-                fromState == Launcher.State.WIDGETS_SPRING_LOADED) {
-            startAnimationToWorkspaceFromWidgets(fromWorkspaceState, toWorkspaceState,
-                    animated, onCompleteRunnable);
-        } else {
-            startAnimationToNewWorkspaceState(fromWorkspaceState, toWorkspaceState,
-                    animated, onCompleteRunnable);
-        }
-    }
-
-    /**
-     * Creates and starts a new animation to a particular overlay view.
-     */
-    private void startAnimationToOverlay(
-            final Workspace.State toWorkspaceState,
-            final View buttonView, final BaseContainerView toView,
-            final boolean animated, int animType, final PrivateTransitionCallbacks pCb) {
-        final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
-        final Resources res = mLauncher.getResources();
-        final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime);
-        final int revealDurationSlide = res.getInteger(R.integer.config_overlaySlideRevealTime);
-
-        final int itemsAlphaStagger = res.getInteger(R.integer.config_overlayItemsAlphaStagger);
-
-        final AnimationLayerSet layerViews = new AnimationLayerSet();
-
-        // If for some reason our views aren't initialized, don't animate
-        boolean initialized = buttonView != null;
-
-        // Cancel the current animation
-        cancelAnimation();
-
-        final View contentView = toView.getContentView();
-        playCommonTransitionAnimations(toWorkspaceState,
-                animated, initialized, animation, layerViews);
-        if (!animated || !initialized) {
-            if (toWorkspaceState == Workspace.State.NORMAL_HIDDEN) {
-                mAllAppsController.finishPullUp();
-            }
-            toView.setTranslationX(0.0f);
-            toView.setTranslationY(0.0f);
-            toView.setScaleX(1.0f);
-            toView.setScaleY(1.0f);
-            toView.setAlpha(1.0f);
-            toView.setVisibility(View.VISIBLE);
-
-            // Show the content view
-            contentView.setVisibility(View.VISIBLE);
-            pCb.onTransitionComplete();
-            return;
-        }
-        if (animType == CIRCULAR_REVEAL) {
-            // Setup the reveal view animation
-            final View revealView = toView.getRevealView();
-
-            int width = revealView.getMeasuredWidth();
-            int height = revealView.getMeasuredHeight();
-            float revealRadius = (float) Math.hypot(width / 2, height / 2);
-            revealView.setVisibility(View.VISIBLE);
-            revealView.setAlpha(0f);
-            revealView.setTranslationY(0f);
-            revealView.setTranslationX(0f);
-
-            // Calculate the final animation values
-            int[] buttonViewToPanelDelta =
-                    Utilities.getCenterDeltaInScreenSpace(revealView, buttonView);
-            final float revealViewToAlpha = pCb.materialRevealViewFinalAlpha;
-            final float revealViewToXDrift = buttonViewToPanelDelta[0];
-            final float revealViewToYDrift = buttonViewToPanelDelta[1];
-
-            // Create the animators
-            PropertyValuesHolder panelAlpha =
-                    PropertyValuesHolder.ofFloat(View.ALPHA, revealViewToAlpha, 1f);
-            PropertyValuesHolder panelDriftY =
-                    PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, revealViewToYDrift, 0);
-            PropertyValuesHolder panelDriftX =
-                    PropertyValuesHolder.ofFloat(View.TRANSLATION_X, revealViewToXDrift, 0);
-            ObjectAnimator panelAlphaAndDrift = ObjectAnimator.ofPropertyValuesHolder(revealView,
-                    panelAlpha, panelDriftY, panelDriftX);
-            panelAlphaAndDrift.setDuration(revealDuration);
-            panelAlphaAndDrift.setInterpolator(new LogDecelerateInterpolator(100, 0));
-
-            // Play the animation
-            layerViews.addView(revealView);
-            animation.play(panelAlphaAndDrift);
-
-            // Setup the animation for the content view
-            contentView.setVisibility(View.VISIBLE);
-            contentView.setAlpha(0f);
-            contentView.setTranslationY(revealViewToYDrift);
-            layerViews.addView(contentView);
-
-            // Create the individual animators
-            ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY",
-                    revealViewToYDrift, 0);
-            pageDrift.setDuration(revealDuration);
-            pageDrift.setInterpolator(new LogDecelerateInterpolator(100, 0));
-            pageDrift.setStartDelay(itemsAlphaStagger);
-            animation.play(pageDrift);
-
-            ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 0f, 1f);
-            itemsAlpha.setDuration(revealDuration);
-            itemsAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
-            itemsAlpha.setStartDelay(itemsAlphaStagger);
-            animation.play(itemsAlpha);
-
-            float startRadius = pCb.getMaterialRevealViewStartFinalRadius();
-            AnimatorListenerAdapter listener = pCb.getMaterialRevealViewAnimatorListener(
-                    revealView, buttonView);
-            Animator reveal = new CircleRevealOutlineProvider(width / 2, height / 2,
-                    startRadius, revealRadius).createRevealAnimator(revealView);
-            reveal.setDuration(revealDuration);
-            reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
-            if (listener != null) {
-                reveal.addListener(listener);
-            }
-            animation.play(reveal);
-
-            animation.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    // Hide the reveal view
-                    revealView.setVisibility(View.INVISIBLE);
-
-                    // This can hold unnecessary references to views.
-                    cleanupAnimation();
-                    pCb.onTransitionComplete();
-                }
-
-            });
-
-            toView.bringToFront();
-            toView.setVisibility(View.VISIBLE);
-
-            animation.addListener(layerViews);
-            toView.post(new StartAnimRunnable(animation, toView));
-            mCurrentAnimation = animation;
-        } else if (animType == PULLUP) {
-            if (!FeatureFlags.LAUNCHER3_PHYSICS) {
-                // We are animating the content view alpha, so ensure we have a layer for it.
-                layerViews.addView(contentView);
-            }
-
-            animation.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    cleanupAnimation();
-                    pCb.onTransitionComplete();
-                }
-            });
-            boolean shouldPost = mAllAppsController.animateToAllApps(animation, revealDurationSlide);
-
-            Runnable startAnimRunnable = new StartAnimRunnable(animation, toView);
-            mCurrentAnimation = animation;
-            mCurrentAnimation.addListener(layerViews);
-            if (shouldPost) {
-                toView.post(startAnimRunnable);
-            } else {
-                startAnimRunnable.run();
-            }
-        }
-    }
-
-    /**
-     * Plays animations used by various transitions.
-     */
-    private void playCommonTransitionAnimations(
-            Workspace.State toWorkspaceState,
-            boolean animated, boolean initialized, AnimatorSet animation,
-            AnimationLayerSet layerViews) {
-        // Create the workspace animation.
-        // NOTE: this call apparently also sets the state for the workspace if !animated
-        Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState,
-                animated, layerViews);
-
-        if (animated && initialized) {
-            // Play the workspace animation
-            if (workspaceAnim != null) {
-                animation.play(workspaceAnim);
-            }
-        }
-    }
-
-    /**
-     * Starts an animation to the workspace from the apps view.
-     */
-    private void startAnimationToWorkspaceFromAllApps(final Workspace.State fromWorkspaceState,
-            final Workspace.State toWorkspaceState, final boolean animated, int type,
-            final Runnable onCompleteRunnable) {
-        // No alpha anim from all apps
-        PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks(1f) {
-            @Override
-            float getMaterialRevealViewStartFinalRadius() {
-                int allAppsButtonSize = mLauncher.getDeviceProfile().allAppsButtonVisualSize;
-                return allAppsButtonSize / 2;
-            }
-            @Override
-            public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(
-                    final View revealView, final View allAppsButtonView) {
-                return new AnimatorListenerAdapter() {
-                    public void onAnimationStart(Animator animation) {
-                        // We set the alpha instead of visibility to ensure that the focus does not
-                        // get taken from the all apps view
-                        allAppsButtonView.setVisibility(View.VISIBLE);
-                        allAppsButtonView.setAlpha(0f);
-                    }
-                    public void onAnimationEnd(Animator animation) {
-                        // Hide the reveal view
-                        revealView.setVisibility(View.INVISIBLE);
-
-                        // Show the all apps button, and focus it
-                        allAppsButtonView.setAlpha(1f);
-                    }
-                };
-            }
-            @Override
-            void onTransitionComplete() {
-                mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
-            }
-        };
-        // Only animate the search bar if animating to spring loaded mode from all apps
-        startAnimationToWorkspaceFromOverlay(fromWorkspaceState, toWorkspaceState,
-                mLauncher.getStartViewForAllAppsRevealAnimation(), mLauncher.getAppsView(),
-                animated, type, onCompleteRunnable, cb);
-    }
-
-    /**
-     * Starts an animation to the workspace from the widgets view.
-     */
-    private void startAnimationToWorkspaceFromWidgets(final Workspace.State fromWorkspaceState,
-            final Workspace.State toWorkspaceState, final boolean animated,
-            final Runnable onCompleteRunnable) {
-        final WidgetsContainerView widgetsView = mLauncher.getWidgetsView();
-        PrivateTransitionCallbacks cb =
-                new PrivateTransitionCallbacks(FINAL_REVEAL_ALPHA_FOR_WIDGETS) {
-            @Override
-            public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(
-                    final View revealView, final View widgetsButtonView) {
-                return new AnimatorListenerAdapter() {
-                    public void onAnimationEnd(Animator animation) {
-                        // Hide the reveal view
-                        revealView.setVisibility(View.INVISIBLE);
-                    }
-                };
-            }
-            @Override
-            void onTransitionComplete() {
-                mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
-            }
-        };
-        startAnimationToWorkspaceFromOverlay(
-                fromWorkspaceState, toWorkspaceState,
-                mLauncher.getWidgetsButton(), widgetsView,
-                animated, CIRCULAR_REVEAL, onCompleteRunnable, cb);
-    }
-
-    /**
-     * Starts an animation to the workspace from another workspace state, e.g. normal to overview.
-     */
-    private void startAnimationToNewWorkspaceState(final Workspace.State fromWorkspaceState,
-            final Workspace.State toWorkspaceState, final boolean animated,
-            final Runnable onCompleteRunnable) {
-        final View fromWorkspace = mLauncher.getWorkspace();
-        final AnimationLayerSet layerViews = new AnimationLayerSet();
-        final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
-
-        // Cancel the current animation
-        cancelAnimation();
-
-        playCommonTransitionAnimations(toWorkspaceState, animated, animated, animation, layerViews);
-        mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
-
-        if (animated) {
-            animation.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    // Run any queued runnables
-                    if (onCompleteRunnable != null) {
-                        onCompleteRunnable.run();
-                    }
-
-                    // This can hold unnecessary references to views.
-                    cleanupAnimation();
-                }
-            });
-            animation.addListener(layerViews);
-            fromWorkspace.post(new StartAnimRunnable(animation, null));
-            mCurrentAnimation = animation;
-        } else /* if (!animated) */ {
-            // Run any queued runnables
-            if (onCompleteRunnable != null) {
-                onCompleteRunnable.run();
-            }
-
-            mCurrentAnimation = null;
-        }
-    }
-
-    /**
-     * Creates and starts a new animation to the workspace.
-     */
-    private void startAnimationToWorkspaceFromOverlay(
-            final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState,
-            final View buttonView, final BaseContainerView fromView,
-            final boolean animated, int animType, final Runnable onCompleteRunnable,
-            final PrivateTransitionCallbacks pCb) {
-        final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
-        final Resources res = mLauncher.getResources();
-        final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime);
-        final int revealDurationSlide = res.getInteger(R.integer.config_overlaySlideRevealTime);
-        final int itemsAlphaStagger = res.getInteger(R.integer.config_overlayItemsAlphaStagger);
-
-        final View toView = mLauncher.getWorkspace();
-        final View revealView = fromView.getRevealView();
-        final View contentView = fromView.getContentView();
-
-        final AnimationLayerSet layerViews = new AnimationLayerSet();
-
-        // If for some reason our views aren't initialized, don't animate
-        boolean initialized = buttonView != null;
-
-        // Cancel the current animation
-        cancelAnimation();
-
-        playCommonTransitionAnimations(toWorkspaceState,
-                animated, initialized, animation, layerViews);
-        if (!animated || !initialized) {
-            if (fromWorkspaceState == Workspace.State.NORMAL_HIDDEN) {
-                mAllAppsController.finishPullDown();
-            }
-            fromView.setVisibility(View.GONE);
-            pCb.onTransitionComplete();
-
-            // Run any queued runnables
-            if (onCompleteRunnable != null) {
-                onCompleteRunnable.run();
-            }
-            return;
-        }
-        if (animType == CIRCULAR_REVEAL) {
-            // hideAppsCustomizeHelper is called in some cases when it is already hidden
-            // don't perform all these no-op animations. In particularly, this was causing
-            // the all-apps button to pop in and out.
-            if (fromView.getVisibility() == View.VISIBLE) {
-                int width = revealView.getMeasuredWidth();
-                int height = revealView.getMeasuredHeight();
-                float revealRadius = (float) Math.hypot(width / 2, height / 2);
-                revealView.setVisibility(View.VISIBLE);
-                revealView.setAlpha(1f);
-                revealView.setTranslationY(0);
-                layerViews.addView(revealView);
-
-                // Calculate the final animation values
-                int[] buttonViewToPanelDelta = Utilities.getCenterDeltaInScreenSpace(revealView, buttonView);
-                final float revealViewToXDrift = buttonViewToPanelDelta[0];
-                final float revealViewToYDrift = buttonViewToPanelDelta[1];
-
-                // The vertical motion of the apps panel should be delayed by one frame
-                // from the conceal animation in order to give the right feel. We correspondingly
-                // shorten the duration so that the slide and conceal end at the same time.
-                TimeInterpolator decelerateInterpolator = new LogDecelerateInterpolator(100, 0);
-                ObjectAnimator panelDriftY = ObjectAnimator.ofFloat(revealView, "translationY",
-                        0, revealViewToYDrift);
-                panelDriftY.setDuration(revealDuration - SINGLE_FRAME_DELAY);
-                panelDriftY.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
-                panelDriftY.setInterpolator(decelerateInterpolator);
-                animation.play(panelDriftY);
-
-                ObjectAnimator panelDriftX = ObjectAnimator.ofFloat(revealView, "translationX",
-                        0, revealViewToXDrift);
-                panelDriftX.setDuration(revealDuration - SINGLE_FRAME_DELAY);
-                panelDriftX.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
-                panelDriftX.setInterpolator(decelerateInterpolator);
-                animation.play(panelDriftX);
-
-                // Setup animation for the reveal panel alpha
-                if (pCb.materialRevealViewFinalAlpha != 1f) {
-                    ObjectAnimator panelAlpha = ObjectAnimator.ofFloat(revealView, "alpha",
-                            1f, pCb.materialRevealViewFinalAlpha);
-                    panelAlpha.setDuration(revealDuration);
-                    panelAlpha.setInterpolator(decelerateInterpolator);
-                    animation.play(panelAlpha);
-                }
-
-                // Setup the animation for the content view
-                layerViews.addView(contentView);
-
-                // Create the individual animators
-                ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY",
-                        0, revealViewToYDrift);
-                contentView.setTranslationY(0);
-                pageDrift.setDuration(revealDuration - SINGLE_FRAME_DELAY);
-                pageDrift.setInterpolator(decelerateInterpolator);
-                pageDrift.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
-                animation.play(pageDrift);
-
-                contentView.setAlpha(1f);
-                ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 1f, 0f);
-                itemsAlpha.setDuration(100);
-                itemsAlpha.setInterpolator(decelerateInterpolator);
-                animation.play(itemsAlpha);
-
-                // Invalidate the scrim throughout the animation to ensure the highlight
-                // cutout is correct throughout.
-                ValueAnimator invalidateScrim = ValueAnimator.ofFloat(0f, 1f);
-                invalidateScrim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-                    @Override
-                    public void onAnimationUpdate(ValueAnimator animation) {
-                        mLauncher.getDragLayer().invalidateScrim();
-                    }
-                });
-                animation.play(invalidateScrim);
-
-                // Animate the all apps button
-                float finalRadius = pCb.getMaterialRevealViewStartFinalRadius();
-                AnimatorListenerAdapter listener =
-                        pCb.getMaterialRevealViewAnimatorListener(revealView, buttonView);
-                Animator reveal = new CircleRevealOutlineProvider(width / 2, height / 2,
-                        revealRadius, finalRadius).createRevealAnimator(revealView);
-                reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
-                reveal.setDuration(revealDuration);
-                reveal.setStartDelay(itemsAlphaStagger);
-                if (listener != null) {
-                    reveal.addListener(listener);
-                }
-                animation.play(reveal);
-            }
-
-            animation.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    fromView.setVisibility(View.GONE);
-                    // Run any queued runnables
-                    if (onCompleteRunnable != null) {
-                        onCompleteRunnable.run();
-                    }
-
-                    // Reset page transforms
-                    if (contentView != null) {
-                        contentView.setTranslationX(0);
-                        contentView.setTranslationY(0);
-                        contentView.setAlpha(1);
-                    }
-
-                    // This can hold unnecessary references to views.
-                    cleanupAnimation();
-                    pCb.onTransitionComplete();
-                }
-            });
-
-            mCurrentAnimation = animation;
-            mCurrentAnimation.addListener(layerViews);
-            fromView.post(new StartAnimRunnable(animation, null));
-        } else if (animType == PULLUP) {
-            // We are animating the content view alpha, so ensure we have a layer for it
-            layerViews.addView(contentView);
-
-            animation.addListener(new AnimatorListenerAdapter() {
-                boolean canceled = false;
-                @Override
-                public void onAnimationCancel(Animator animation) {
-                    canceled = true;
-                }
-
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    if (canceled) return;
-                    // Run any queued runnables
-                    if (onCompleteRunnable != null) {
-                        onCompleteRunnable.run();
-                    }
-
-                    cleanupAnimation();
-                    pCb.onTransitionComplete();
-                }
-
-            });
-            boolean shouldPost = mAllAppsController.animateToWorkspace(animation, revealDurationSlide);
-
-            Runnable startAnimRunnable = new StartAnimRunnable(animation, toView);
-            mCurrentAnimation = animation;
-            mCurrentAnimation.addListener(layerViews);
-            if (shouldPost) {
-                fromView.post(startAnimRunnable);
-            } else {
-                startAnimRunnable.run();
-            }
-        }
-        return;
-    }
-
-    /**
-     * Cancels the current animation.
-     */
-    private void cancelAnimation() {
-        if (mCurrentAnimation != null) {
-            mCurrentAnimation.setDuration(0);
-            mCurrentAnimation.cancel();
-            mCurrentAnimation = null;
-        }
-    }
-
-    @Thunk void cleanupAnimation() {
-        mCurrentAnimation = null;
-    }
-
-    private class StartAnimRunnable implements Runnable {
-
-        private final AnimatorSet mAnim;
-        private final View mViewToFocus;
-
-        public StartAnimRunnable(AnimatorSet anim, View viewToFocus) {
-            mAnim = anim;
-            mViewToFocus = viewToFocus;
-        }
-
-        @Override
-        public void run() {
-            if (mCurrentAnimation != mAnim) {
-                return;
-            }
-            if (mViewToFocus != null) {
-                mViewToFocus.requestFocus();
-            }
-            mAnim.start();
-        }
-    }
-}
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 87f3dda..6c22474 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -46,10 +46,10 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.animation.Interpolator;
 
+import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.anim.PropertyListBuilder;
 import com.android.launcher3.pageindicators.PageIndicator;
 import com.android.launcher3.touch.OverScroll;
-import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.Thunk;
 
 import java.util.ArrayList;
@@ -87,15 +87,12 @@
     public static final int INVALID_RESTORE_PAGE = -1001;
 
     private boolean mFreeScroll = false;
-    private int mFreeScrollMinScrollX = -1;
-    private int mFreeScrollMaxScrollX = -1;
 
     protected int mFlingThresholdVelocity;
     protected int mMinFlingVelocity;
     protected int mMinSnapVelocity;
 
     protected boolean mFirstLayout = true;
-    private int mNormalChildHeight;
 
     @ViewDebug.ExportedProperty(category = "launcher")
     protected int mCurrentPage;
@@ -137,7 +134,6 @@
     protected int mTouchSlop;
     private int mMaximumVelocity;
     protected boolean mAllowOverScroll = true;
-    protected int[] mTempVisiblePagesRange = new int[2];
 
     protected static final int INVALID_POINTER = -1;
 
@@ -169,8 +165,6 @@
     @Thunk static int REORDERING_REORDER_REPOSITION_DURATION = 300;
     private static int REORDERING_SIDE_PAGE_HOVER_TIMEOUT = 80;
 
-    private float mMinScale = 1f;
-    private boolean mUseMinScale = false;
     @Thunk View mDragView;
     private Runnable mSidePageHoverRunnable;
     @Thunk int mSidePageHoverIndex = -1;
@@ -218,7 +212,7 @@
      */
     protected void init() {
         mScroller = new LauncherScroller(getContext());
-        setDefaultInterpolator(new ScrollInterpolator());
+        setDefaultInterpolator(Interpolators.SCROLL);
         mCurrentPage = 0;
 
         final ViewConfiguration configuration = ViewConfiguration.get(getContext());
@@ -276,12 +270,6 @@
         }
     }
 
-    public void setMinScale(float f) {
-        mMinScale = f;
-        mUseMinScale = true;
-        requestLayout();
-    }
-
     @Override
     public void setScaleX(float scaleX) {
         super.setScaleX(scaleX);
@@ -380,16 +368,8 @@
     }
 
     private int validateNewPage(int newPage) {
-        int validatedPage = newPage;
-        // When in free scroll mode, we need to clamp to the free scroll page range.
-        if (mFreeScroll) {
-            getFreeScrollPageRange(mTempVisiblePagesRange);
-            validatedPage = Math.max(mTempVisiblePagesRange[0],
-                    Math.min(newPage, mTempVisiblePagesRange[1]));
-        }
         // Ensure that it is clamped by the actual set of children in all cases
-        validatedPage = Utilities.boundToRange(validatedPage, 0, getPageCount() - 1);
-        return validatedPage;
+        return Utilities.boundToRange(newPage, 0, getPageCount() - 1);
     }
 
     /**
@@ -491,13 +471,11 @@
         if (mFreeScroll) {
             // If the scroller is trying to move to a location beyond the maximum allowed
             // in the free scroll mode, we make sure to end the scroll operation.
-            if (!mScroller.isFinished() &&
-                    (x > mFreeScrollMaxScrollX || x < mFreeScrollMinScrollX)) {
+            if (!mScroller.isFinished() && (x > mMaxScrollX || x < 0)) {
                 forceFinishScroller(false);
             }
 
-            x = Math.min(x, mFreeScrollMaxScrollX);
-            x = Math.max(x, mFreeScrollMinScrollX);
+            x = Utilities.boundToRange(x, 0, mMaxScrollX);
         }
 
         mUnboundedScrollX = x;
@@ -610,56 +588,9 @@
         computeScrollHelper();
     }
 
-    public static class LayoutParams extends ViewGroup.LayoutParams {
-        public boolean isFullScreenPage = false;
-
-        // If true, the start edge of the page snaps to the start edge of the viewport.
-        public boolean matchStartEdge = false;
-
-        /**
-         * {@inheritDoc}
-         */
-        public LayoutParams(int width, int height) {
-            super(width, height);
-        }
-
-        public LayoutParams(Context context, AttributeSet attrs) {
-            super(context, attrs);
-        }
-
-        public LayoutParams(ViewGroup.LayoutParams source) {
-            super(source);
-        }
-    }
-
-    @Override
-    public LayoutParams generateLayoutParams(AttributeSet attrs) {
-        return new LayoutParams(getContext(), attrs);
-    }
-
-    @Override
-    protected LayoutParams generateDefaultLayoutParams() {
-        return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
-    }
-
-    @Override
-    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
-        return new LayoutParams(p);
-    }
-
-    @Override
-    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
-        return p instanceof LayoutParams;
-    }
-
-    public void addFullScreenPage(View page) {
-        LayoutParams lp = generateDefaultLayoutParams();
-        lp.isFullScreenPage = true;
-        super.addView(page, 0, lp);
-    }
-
     public int getNormalChildHeight() {
-        return mNormalChildHeight;
+        return  getViewportHeight() - getPaddingTop() - getPaddingBottom()
+                - mInsets.top - mInsets.bottom;
     }
 
     @Override
@@ -675,22 +606,7 @@
         int widthSize = MeasureSpec.getSize(widthMeasureSpec);
         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
         int heightSize = MeasureSpec.getSize(heightMeasureSpec);
-        // NOTE: We multiply by 2f to account for the fact that depending on the offset of the
-        // viewport, we can be at most one and a half screens offset once we scale down
-        DisplayMetrics dm = getResources().getDisplayMetrics();
-        int maxSize = Math.max(dm.widthPixels + mInsets.left + mInsets.right,
-                dm.heightPixels + mInsets.top + mInsets.bottom);
 
-        int parentWidthSize = (int) (2f * maxSize);
-        int parentHeightSize = (int) (2f * maxSize);
-        int scaledWidthSize, scaledHeightSize;
-        if (mUseMinScale) {
-            scaledWidthSize = (int) (parentWidthSize / mMinScale);
-            scaledHeightSize = (int) (parentHeightSize / mMinScale);
-        } else {
-            scaledWidthSize = widthSize;
-            scaledHeightSize = heightSize;
-        }
         mViewport.set(0, 0, widthSize, heightSize);
 
         if (widthMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.UNSPECIFIED) {
@@ -704,71 +620,19 @@
             return;
         }
 
-        /* Allow the height to be set as WRAP_CONTENT. This allows the particular case
-         * of the All apps view on XLarge displays to not take up more space then it needs. Width
-         * is still not allowed to be set as WRAP_CONTENT since many parts of the code expect
-         * each page to have the same width.
-         */
-        final int verticalPadding = getPaddingTop() + getPaddingBottom();
-        final int horizontalPadding = getPaddingLeft() + getPaddingRight();
-
-        int referenceChildWidth = 0;
         // The children are given the same width and height as the workspace
         // unless they were set to WRAP_CONTENT
         if (DEBUG) Log.d(TAG, "PagedView.onMeasure(): " + widthSize + ", " + heightSize);
-        if (DEBUG) Log.d(TAG, "PagedView.scaledSize: " + scaledWidthSize + ", " + scaledHeightSize);
-        if (DEBUG) Log.d(TAG, "PagedView.parentSize: " + parentWidthSize + ", " + parentHeightSize);
-        if (DEBUG) Log.d(TAG, "PagedView.horizontalPadding: " + horizontalPadding);
-        if (DEBUG) Log.d(TAG, "PagedView.verticalPadding: " + verticalPadding);
-        final int childCount = getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            // disallowing padding in paged view (just pass 0)
-            final View child = getPageAt(i);
-            if (child.getVisibility() != GONE) {
-                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
 
-                int childWidthMode;
-                int childHeightMode;
-                int childWidth;
-                int childHeight;
+        int myWidthSpec = MeasureSpec.makeMeasureSpec(
+                getViewportWidth() - mInsets.left - mInsets.right, MeasureSpec.EXACTLY);
+        int myHeightSpec = MeasureSpec.makeMeasureSpec(
+                getViewportHeight() - mInsets.top - mInsets.bottom, MeasureSpec.EXACTLY);
 
-                if (!lp.isFullScreenPage) {
-                    if (lp.width == LayoutParams.WRAP_CONTENT) {
-                        childWidthMode = MeasureSpec.AT_MOST;
-                    } else {
-                        childWidthMode = MeasureSpec.EXACTLY;
-                    }
-
-                    if (lp.height == LayoutParams.WRAP_CONTENT) {
-                        childHeightMode = MeasureSpec.AT_MOST;
-                    } else {
-                        childHeightMode = MeasureSpec.EXACTLY;
-                    }
-
-                    childWidth = getViewportWidth() - horizontalPadding
-                            - mInsets.left - mInsets.right;
-                    childHeight = getViewportHeight() - verticalPadding
-                            - mInsets.top - mInsets.bottom;
-                    mNormalChildHeight = childHeight;
-                } else {
-                    childWidthMode = MeasureSpec.EXACTLY;
-                    childHeightMode = MeasureSpec.EXACTLY;
-
-                    childWidth = getViewportWidth();
-                    childHeight = getViewportHeight();
-                }
-                if (referenceChildWidth == 0) {
-                    referenceChildWidth = childWidth;
-                }
-
-                final int childWidthMeasureSpec =
-                        MeasureSpec.makeMeasureSpec(childWidth, childWidthMode);
-                    final int childHeightMeasureSpec =
-                        MeasureSpec.makeMeasureSpec(childHeight, childHeightMode);
-                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
-            }
-        }
-        setMeasuredDimension(scaledWidthSize, scaledHeightSize);
+        // measureChildren takes accounts for content padding, we only need to care about extra
+        // space due to insets.
+        measureChildren(myWidthSpec, myHeightSpec);
+        setMeasuredDimension(widthSize, heightSize);
     }
 
     @SuppressLint("DrawAllocation")
@@ -793,10 +657,7 @@
 
         int verticalPadding = getPaddingTop() + getPaddingBottom();
 
-        LayoutParams lp = (LayoutParams) getChildAt(startIndex).getLayoutParams();
-        LayoutParams nextLp;
-
-        int childLeft = offsetX + (lp.isFullScreenPage ? 0 : getPaddingLeft());
+        int childLeft = offsetX + getPaddingLeft();
         if (mPageScrolls == null || childCount != mChildCountOnLastLayout) {
             mPageScrolls = new int[childCount];
         }
@@ -804,14 +665,9 @@
         for (int i = startIndex; i != endIndex; i += delta) {
             final View child = getPageAt(i);
             if (child.getVisibility() != View.GONE) {
-                lp = (LayoutParams) child.getLayoutParams();
-                int childTop;
-                if (lp.isFullScreenPage) {
-                    childTop = offsetY;
-                } else {
-                    childTop = offsetY + getPaddingTop() + mInsets.top;
-                    childTop += (getViewportHeight() - mInsets.top - mInsets.bottom - verticalPadding - child.getMeasuredHeight()) / 2;
-                }
+                int childTop = offsetY + getPaddingTop() + mInsets.top;
+                childTop += (getViewportHeight() - mInsets.top - mInsets.bottom - verticalPadding
+                        - child.getMeasuredHeight()) / 2;
 
                 final int childWidth = child.getMeasuredWidth();
                 final int childHeight = child.getMeasuredHeight();
@@ -820,26 +676,10 @@
                 child.layout(childLeft, childTop,
                         childLeft + child.getMeasuredWidth(), childTop + childHeight);
 
-                int scrollOffsetLeft = lp.isFullScreenPage ? 0 : getPaddingLeft();
+                int scrollOffsetLeft = getPaddingLeft();
                 mPageScrolls[i] = childLeft - scrollOffsetLeft - offsetX;
 
-                int pageGap = mPageSpacing;
-                int next = i + delta;
-                if (next != endIndex) {
-                    nextLp = (LayoutParams) getPageAt(next).getLayoutParams();
-                } else {
-                    nextLp = null;
-                }
-
-                // Prevent full screen pages from showing in the viewport
-                // when they are not the current page.
-                if (lp.isFullScreenPage) {
-                    pageGap = getPaddingLeft();
-                } else if (nextLp != null && nextLp.isFullScreenPage) {
-                    pageGap = getPaddingRight();
-                }
-
-                childLeft += childWidth + pageGap + getChildGap();
+                childLeft += childWidth + mPageSpacing + getChildGap();
             }
         }
 
@@ -886,7 +726,7 @@
         return 0;
     }
 
-    @Thunk void updateMaxScrollX() {
+    private void updateMaxScrollX() {
         mMaxScrollX = computeMaxScrollX();
     }
 
@@ -915,13 +755,11 @@
 
         // This ensures that when children are added, they get the correct transforms / alphas
         // in accordance with any scroll effects.
-        updateFreescrollBounds();
         invalidate();
     }
 
     @Override
     public void onChildViewRemoved(View parent, View child) {
-        updateFreescrollBounds();
         mCurrentPage = validateNewPage(mCurrentPage);
         invalidate();
     }
@@ -974,11 +812,6 @@
         return offset;
     }
 
-    protected void getFreeScrollPageRange(int[] range) {
-        range[0] = 0;
-        range[1] = Math.max(0, getChildCount() - 1);
-    }
-
     @Override
     public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
         int page = indexToPage(indexOfChild(child));
@@ -1309,12 +1142,7 @@
         } else {
             View child = getChildAt(index);
 
-            int scrollOffset = 0;
-            LayoutParams lp = (LayoutParams) child.getLayoutParams();
-            if (!lp.isFullScreenPage) {
-                scrollOffset = mIsRtl ? getPaddingRight() : getPaddingLeft();
-            }
-
+            int scrollOffset = scrollOffset = mIsRtl ? getPaddingRight() : getPaddingLeft();
             int baselineX = mPageScrolls[index] + scrollOffset + getViewportOffsetX();
             return (int) (child.getX() - baselineX);
         }
@@ -1341,38 +1169,20 @@
     /**
      * return true if freescroll has been enabled, false otherwise
      */
-    public boolean enableFreeScroll() {
+    protected void enableFreeScroll() {
         setEnableFreeScroll(true);
-        return true;
     }
 
-    public void disableFreeScroll() {
+    protected void disableFreeScroll() {
         setEnableFreeScroll(false);
     }
 
-    void updateFreescrollBounds() {
-        getFreeScrollPageRange(mTempVisiblePagesRange);
-        if (mIsRtl) {
-            mFreeScrollMinScrollX = getScrollForPage(mTempVisiblePagesRange[1]);
-            mFreeScrollMaxScrollX = getScrollForPage(mTempVisiblePagesRange[0]);
-        } else {
-            mFreeScrollMinScrollX = getScrollForPage(mTempVisiblePagesRange[0]);
-            mFreeScrollMaxScrollX = getScrollForPage(mTempVisiblePagesRange[1]);
-        }
-    }
-
     private void setEnableFreeScroll(boolean freeScroll) {
         boolean wasFreeScroll = mFreeScroll;
         mFreeScroll = freeScroll;
 
         if (mFreeScroll) {
-            updateFreescrollBounds();
-            getFreeScrollPageRange(mTempVisiblePagesRange);
-            if (getCurrentPage() < mTempVisiblePagesRange[0]) {
-                setCurrentPage(mTempVisiblePagesRange[0]);
-            } else if (getCurrentPage() > mTempVisiblePagesRange[1]) {
-                setCurrentPage(mTempVisiblePagesRange[1]);
-            }
+            setCurrentPage(getNextPage());
         } else if (wasFreeScroll) {
             snapToPage(getNextPage());
         }
@@ -1388,12 +1198,12 @@
         if (mDragView != null) {
             int dragX = (int) (mDragView.getLeft() + (mDragView.getMeasuredWidth() / 2)
                     + mDragView.getTranslationX());
-            getFreeScrollPageRange(mTempVisiblePagesRange);
             int minDistance = Integer.MAX_VALUE;
             int minIndex = indexOfChild(mDragView);
-            for (int i = mTempVisiblePagesRange[0]; i <= mTempVisiblePagesRange[1]; i++) {
+            int maxPageNo = getChildCount() - 1;
+            for (int i = 0; i <= maxPageNo; i++) {
                 View page = getPageAt(i);
-                int pageX = (int) (page.getLeft() + page.getMeasuredWidth() / 2);
+                int pageX = (page.getLeft() + page.getMeasuredWidth() / 2);
                 int d = Math.abs(dragX - pageX);
                 if (d < minDistance) {
                     minIndex = i;
@@ -1488,11 +1298,7 @@
                 final int pageUnderPointIndex = getNearestHoverOverPageIndex();
                 // Do not allow any page to be moved to 0th position.
                 if (pageUnderPointIndex > 0 && pageUnderPointIndex != indexOfChild(mDragView)) {
-                    mTempVisiblePagesRange[0] = 0;
-                    mTempVisiblePagesRange[1] = getPageCount() - 1;
-                    getFreeScrollPageRange(mTempVisiblePagesRange);
-                    if (mTempVisiblePagesRange[0] <= pageUnderPointIndex &&
-                            pageUnderPointIndex <= mTempVisiblePagesRange[1] &&
+                    if (0 <= pageUnderPointIndex && pageUnderPointIndex <= getPageCount() - 1 &&
                             pageUnderPointIndex != mSidePageHoverIndex && mScroller.isFinished()) {
                         mSidePageHoverIndex = pageUnderPointIndex;
                         mSidePageHoverRunnable = new Runnable() {
@@ -1809,16 +1615,6 @@
         return PAGE_SNAP_ANIMATION_DURATION;
     }
 
-    public static class ScrollInterpolator implements Interpolator {
-        public ScrollInterpolator() {
-        }
-
-        public float getInterpolation(float t) {
-            t -= 1.0f;
-            return t*t*t*t*t + 1;
-        }
-    }
-
     // We want the duration of the page snap animation to be influenced by the distance that
     // the screen has to travel, however, we don't want this duration to be effected in a
     // purely linear fashion. Instead, we use this method to moderate the effect that the distance
@@ -2026,18 +1822,14 @@
         // Do not allow the first page to be moved around
         if (mTouchState != TOUCH_STATE_REST || dragViewIndex <= 0) return false;
 
-        mTempVisiblePagesRange[0] = 0;
-        mTempVisiblePagesRange[1] = getPageCount() - 1;
-        getFreeScrollPageRange(mTempVisiblePagesRange);
-        mReorderingStarted = true;
-
         // Check if we are within the reordering range
-        if (mTempVisiblePagesRange[0] <= dragViewIndex &&
-            dragViewIndex <= mTempVisiblePagesRange[1]) {
+        if (0 <= dragViewIndex && dragViewIndex <= getPageCount() - 1) {
             // Find the drag view under the pointer
             mDragView = getChildAt(dragViewIndex);
             mDragView.animate().scaleX(1.15f).scaleY(1.15f).setDuration(100).start();
             mDragViewBaselineLeft = mDragView.getLeft();
+            mReorderingStarted = true;
+
             snapToPage(getPageNearestToCenterOfScreen());
             disableFreeScroll();
             onStartReordering();
diff --git a/src/com/android/launcher3/PendingAppWidgetHostView.java b/src/com/android/launcher3/PendingAppWidgetHostView.java
index c2d5501..b86d413 100644
--- a/src/com/android/launcher3/PendingAppWidgetHostView.java
+++ b/src/com/android/launcher3/PendingAppWidgetHostView.java
@@ -110,9 +110,7 @@
         mClickListener = l;
     }
 
-    @Override
-    public boolean isReinflateRequired(int orientation) {
-        // Re inflate is required any time the widget restore status changes
+    public boolean isReinflateIfNeeded() {
         return mStartState != mInfo.restoreStatus;
     }
 
diff --git a/src/com/android/launcher3/PinchAnimationManager.java b/src/com/android/launcher3/PinchAnimationManager.java
deleted file mode 100644
index c3d3bb3..0000000
--- a/src/com/android/launcher3/PinchAnimationManager.java
+++ /dev/null
@@ -1,244 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
-import android.util.Log;
-import android.view.View;
-import android.view.animation.LinearInterpolator;
-
-import com.android.launcher3.anim.AnimationLayerSet;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-
-import static com.android.launcher3.Workspace.State.NORMAL;
-import static com.android.launcher3.Workspace.State.OVERVIEW;
-
-/**
- * Manages the animations that play as the user pinches to/from overview mode.
- *
- *  It will look like this pinching in:
- * - Workspace scales down
- * - At some threshold 1, hotseat and QSB fade out (full animation)
- * - At a later threshold 2, panel buttons fade in and scrim fades in
- * - At a final threshold 3, snap to overview
- *
- * Pinching out:
- * - Workspace scales up
- * - At threshold 1, panel buttons fade out
- * - At threshold 2, hotseat and QSB fade in and scrim fades out
- * - At threshold 3, snap to workspace
- *
- * @see PinchToOverviewListener
- * @see PinchThresholdManager
- */
-public class PinchAnimationManager {
-    private static final String TAG = "PinchAnimationManager";
-
-    private static final int THRESHOLD_ANIM_DURATION = 150;
-    private static final LinearInterpolator INTERPOLATOR = new LinearInterpolator();
-
-    private static final int INDEX_HOTSEAT = 0;
-    private static final int INDEX_OVERVIEW_PANEL_BUTTONS = 1;
-    private static final int INDEX_SCRIM = 2;
-
-    private final Animator[] mAnimators = new Animator[3];
-
-    private Launcher mLauncher;
-    private Workspace mWorkspace;
-
-    private float mOverviewScale;
-    private float mOverviewTranslationY;
-    private int mNormalOverviewTransitionDuration;
-    private boolean mIsAnimating;
-
-    public PinchAnimationManager(Launcher launcher) {
-        mLauncher = launcher;
-        mWorkspace = launcher.mWorkspace;
-
-        mOverviewScale = mWorkspace.getOverviewModeShrinkFactor();
-        mOverviewTranslationY = mWorkspace.getOverviewModeTranslationY();
-        mNormalOverviewTransitionDuration = mWorkspace.getStateTransitionAnimation()
-                .mOverviewTransitionTime;
-    }
-
-    public int getNormalOverviewTransitionDuration() {
-        return mNormalOverviewTransitionDuration;
-    }
-
-    /**
-     * Interpolate from {@param currentProgress} to {@param toProgress}, calling
-     * {@link #setAnimationProgress(float)} throughout the duration. If duration is -1,
-     * the default overview transition duration is used.
-     */
-    public void animateToProgress(float currentProgress, float toProgress, int duration,
-            final PinchThresholdManager thresholdManager) {
-        if (duration == -1) {
-            duration = mNormalOverviewTransitionDuration;
-        }
-        ValueAnimator animator = ValueAnimator.ofFloat(currentProgress, toProgress);
-        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-                @Override
-                public void onAnimationUpdate(ValueAnimator animation) {
-                    float pinchProgress = (Float) animation.getAnimatedValue();
-                    setAnimationProgress(pinchProgress);
-                    thresholdManager.updateAndAnimatePassedThreshold(pinchProgress,
-                            PinchAnimationManager.this);
-                }
-            }
-        );
-        animator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mIsAnimating = false;
-                thresholdManager.reset();
-                mWorkspace.onEndStateTransition();
-            }
-        });
-        animator.setDuration(duration).start();
-        mIsAnimating = true;
-    }
-
-    public boolean isAnimating() {
-        return mIsAnimating;
-    }
-
-    /**
-     * Animates to the specified progress. This should be called repeatedly throughout the pinch
-     * gesture to run animations that interpolate throughout the gesture.
-     * @param interpolatedProgress The progress from 0 to 1, where 0 is overview and 1 is workspace.
-     */
-    public void setAnimationProgress(float interpolatedProgress) {
-        float interpolatedScale = interpolatedProgress * (1f - mOverviewScale) + mOverviewScale;
-        float interpolatedTranslationY = (1f - interpolatedProgress) * mOverviewTranslationY;
-        mWorkspace.setScaleX(interpolatedScale);
-        mWorkspace.setScaleY(interpolatedScale);
-        mWorkspace.setTranslationY(interpolatedTranslationY);
-        setOverviewPanelsAlpha(1f - interpolatedProgress, 0);
-    }
-
-    /**
-     * Animates certain properties based on which threshold was passed, and in what direction. The
-     * starting state must also be taken into account because the thresholds mean different things
-     * when going from workspace to overview and vice versa.
-     * @param threshold One of {@link PinchThresholdManager#THRESHOLD_ONE},
-     *                  {@link PinchThresholdManager#THRESHOLD_TWO}, or
-     *                  {@link PinchThresholdManager#THRESHOLD_THREE}
-     * @param startState {@link Workspace.State#NORMAL} or {@link Workspace.State#OVERVIEW}.
-     * @param goingTowards {@link Workspace.State#NORMAL} or {@link Workspace.State#OVERVIEW}.
-     *                     Note that this doesn't have to be the opposite of startState;
-     */
-    public void animateThreshold(float threshold, Workspace.State startState,
-            Workspace.State goingTowards) {
-        if (threshold == PinchThresholdManager.THRESHOLD_ONE) {
-            if (startState == OVERVIEW) {
-                animateOverviewPanelButtons(goingTowards == OVERVIEW);
-            } else if (startState == NORMAL) {
-                animateHotseatAndQsb(goingTowards == NORMAL);
-            }
-        } else if (threshold == PinchThresholdManager.THRESHOLD_TWO) {
-            if (startState == OVERVIEW) {
-                animateHotseatAndQsb(goingTowards == NORMAL);
-                animateScrim(goingTowards == OVERVIEW);
-            } else if (startState == NORMAL) {
-                animateOverviewPanelButtons(goingTowards == OVERVIEW);
-                animateScrim(goingTowards == OVERVIEW);
-            }
-        } else if (threshold == PinchThresholdManager.THRESHOLD_THREE) {
-            // Passing threshold 3 ends the pinch and snaps to the new state.
-            if (startState == OVERVIEW && goingTowards == NORMAL) {
-                mLauncher.getUserEventDispatcher().logActionOnContainer(
-                        Action.Touch.PINCH, Action.Direction.NONE,
-                        ContainerType.OVERVIEW, mWorkspace.getCurrentPage());
-                mLauncher.showWorkspace(true);
-                mWorkspace.snapToPage(mWorkspace.getCurrentPage());
-            } else if (startState == NORMAL && goingTowards == OVERVIEW) {
-                mLauncher.getUserEventDispatcher().logActionOnContainer(
-                        Action.Touch.PINCH, Action.Direction.NONE,
-                        ContainerType.WORKSPACE, mWorkspace.getCurrentPage());
-                mLauncher.showOverviewMode(true);
-            }
-        } else {
-            Log.e(TAG, "Received unknown threshold to animate: " + threshold);
-        }
-    }
-
-    private void setOverviewPanelsAlpha(float alpha, int duration) {
-        int childCount = mWorkspace.getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            final CellLayout cl = (CellLayout) mWorkspace.getChildAt(i);
-            if (duration == 0) {
-                cl.setBackgroundAlpha(alpha);
-            } else {
-                ObjectAnimator.ofFloat(cl, "backgroundAlpha", alpha).setDuration(duration).start();
-            }
-        }
-    }
-
-    private void animateHotseatAndQsb(boolean show) {
-        startAnimator(INDEX_HOTSEAT,
-                mWorkspace.createHotseatAlphaAnimator(show ? 1 : 0), THRESHOLD_ANIM_DURATION);
-    }
-
-    private void animateOverviewPanelButtons(boolean show) {
-        animateShowHideView(INDEX_OVERVIEW_PANEL_BUTTONS, mLauncher.getOverviewPanel(), show);
-    }
-
-    private void animateScrim(boolean show) {
-        float endValue = show ? mWorkspace.getStateTransitionAnimation().mWorkspaceScrimAlpha : 0;
-        startAnimator(INDEX_SCRIM,
-                ObjectAnimator.ofFloat(mLauncher.getDragLayer(), "backgroundAlpha", endValue),
-                mNormalOverviewTransitionDuration);
-    }
-
-    private void animateShowHideView(int index, final View view, boolean show) {
-        Animator animator = ObjectAnimator.ofFloat(view, View.ALPHA, show ? 1 : 0);
-        animator.addListener(new AnimationLayerSet(view));
-        if (show) {
-            view.setVisibility(View.VISIBLE);
-        } else {
-            animator.addListener(new AnimatorListenerAdapter() {
-                private boolean mCancelled = false;
-
-                @Override
-                public void onAnimationCancel(Animator animation) {
-                    mCancelled = true;
-                }
-
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    if (!mCancelled) {
-                        view.setVisibility(View.INVISIBLE);
-                    }
-                }
-            });
-        }
-        startAnimator(index, animator, THRESHOLD_ANIM_DURATION);
-    }
-
-    private void startAnimator(int index, Animator animator, long duration) {
-        if (mAnimators[index] != null) {
-            mAnimators[index].cancel();
-        }
-        mAnimators[index] = animator;
-        mAnimators[index].setInterpolator(INTERPOLATOR);
-        mAnimators[index].setDuration(duration).start();
-    }
-}
diff --git a/src/com/android/launcher3/PinchThresholdManager.java b/src/com/android/launcher3/PinchThresholdManager.java
deleted file mode 100644
index 52aac17..0000000
--- a/src/com/android/launcher3/PinchThresholdManager.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2016 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;
-
-/**
- * Keeps track of when thresholds are passed during a pinch gesture,
- * used to inform {@link PinchAnimationManager} throughout.
- *
- * @see PinchToOverviewListener
- * @see PinchAnimationManager
- */
-public class PinchThresholdManager {
-    public static final float THRESHOLD_ZERO = 0.0f;
-    public static final float THRESHOLD_ONE = 0.40f;
-    public static final float THRESHOLD_TWO = 0.70f;
-    public static final float THRESHOLD_THREE = 0.95f;
-
-    private Workspace mWorkspace;
-
-    private float mPassedThreshold = THRESHOLD_ZERO;
-
-    public PinchThresholdManager(Workspace workspace) {
-        mWorkspace = workspace;
-    }
-
-    /**
-     * Uses the pinch progress to determine whether a threshold has been passed,
-     * and asks the {@param animationManager} to animate if so.
-     * @param progress From 0 to 1, where 0 is overview and 1 is workspace.
-     * @param animationManager Animates the threshold change if one is passed.
-     * @return The last passed threshold, one of
-     *         {@link PinchThresholdManager#THRESHOLD_ZERO},
-     *         {@link PinchThresholdManager#THRESHOLD_ONE},
-     *         {@link PinchThresholdManager#THRESHOLD_TWO}, or
-     *         {@link PinchThresholdManager#THRESHOLD_THREE}
-     */
-    public float updateAndAnimatePassedThreshold(float progress,
-            PinchAnimationManager animationManager) {
-        if (!mWorkspace.isInOverviewMode()) {
-            // Invert the progress, because going from workspace to overview is 1 to 0.
-            progress = 1f - progress;
-        }
-
-        float previousPassedThreshold = mPassedThreshold;
-
-        if (progress < THRESHOLD_ONE) {
-            mPassedThreshold = THRESHOLD_ZERO;
-        } else if (progress < THRESHOLD_TWO) {
-            mPassedThreshold = THRESHOLD_ONE;
-        } else if (progress < THRESHOLD_THREE) {
-            mPassedThreshold = THRESHOLD_TWO;
-        } else {
-            mPassedThreshold = THRESHOLD_THREE;
-        }
-
-        if (mPassedThreshold != previousPassedThreshold) {
-            Workspace.State fromState = mWorkspace.isInOverviewMode() ? Workspace.State.OVERVIEW
-                    : Workspace.State.NORMAL;
-            Workspace.State toState = mWorkspace.isInOverviewMode() ? Workspace.State.NORMAL
-                    : Workspace.State.OVERVIEW;
-            float thresholdToAnimate = mPassedThreshold;
-            if (mPassedThreshold < previousPassedThreshold) {
-                // User reversed pinch, so heading back to the state that they started from.
-                toState = fromState;
-                thresholdToAnimate = previousPassedThreshold;
-            }
-            animationManager.animateThreshold(thresholdToAnimate, fromState, toState);
-        }
-        return mPassedThreshold;
-    }
-
-    public float getPassedThreshold() {
-        return mPassedThreshold;
-    }
-
-    public void reset() {
-        mPassedThreshold = THRESHOLD_ZERO;
-    }
-}
diff --git a/src/com/android/launcher3/PinchToOverviewListener.java b/src/com/android/launcher3/PinchToOverviewListener.java
deleted file mode 100644
index 42515d1..0000000
--- a/src/com/android/launcher3/PinchToOverviewListener.java
+++ /dev/null
@@ -1,215 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3;
-
-import android.animation.TimeInterpolator;
-import android.content.Context;
-import android.view.MotionEvent;
-import android.view.ScaleGestureDetector;
-
-import com.android.launcher3.util.TouchController;
-
-/**
- * Detects pinches and animates the Workspace to/from overview mode.
- *
- * Usage: Pass MotionEvents to onInterceptTouchEvent() and onTouchEvent(). This class will handle
- * the pinch detection, and use {@link PinchAnimationManager} to handle the animations.
- *
- * @see PinchThresholdManager
- * @see PinchAnimationManager
- */
-public class PinchToOverviewListener extends ScaleGestureDetector.SimpleOnScaleGestureListener
-        implements TouchController {
-    private static final float OVERVIEW_PROGRESS = 0f;
-    private static final float WORKSPACE_PROGRESS = 1f;
-    /**
-     * The velocity threshold at which a pinch will be completed instead of canceled,
-     * even if the first threshold has not been passed. Measured in progress / millisecond
-     */
-    private static final float FLING_VELOCITY = 0.003f;
-
-    private ScaleGestureDetector mPinchDetector;
-    private Launcher mLauncher;
-    private Workspace mWorkspace = null;
-    private boolean mPinchStarted = false;
-    private float mPreviousProgress;
-    private float mProgressDelta;
-    private long mPreviousTimeMillis;
-    private long mTimeDelta;
-    private boolean mPinchCanceled = false;
-    private TimeInterpolator mInterpolator;
-
-    private PinchThresholdManager mThresholdManager;
-    private PinchAnimationManager mAnimationManager;
-
-    public PinchToOverviewListener(Launcher launcher) {
-        mLauncher = launcher;
-        mPinchDetector = new ScaleGestureDetector((Context) mLauncher, this);
-    }
-
-    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
-        mPinchDetector.onTouchEvent(ev);
-        return mPinchStarted;
-    }
-
-    public boolean onControllerTouchEvent(MotionEvent ev) {
-        if (mPinchStarted) {
-            if (ev.getPointerCount() > 2) {
-                // Using more than two fingers causes weird behavior, so just cancel the pinch.
-                cancelPinch(mPreviousProgress, -1);
-            } else {
-                return mPinchDetector.onTouchEvent(ev);
-            }
-        }
-        return false;
-    }
-
-    @Override
-    public boolean onScaleBegin(ScaleGestureDetector detector) {
-        if (mLauncher.mState != Launcher.State.WORKSPACE || mLauncher.isOnCustomContent()) {
-            // Don't listen for the pinch gesture if on all apps, widget picker, -1, etc.
-            return false;
-        }
-        if (mAnimationManager != null && mAnimationManager.isAnimating()) {
-            // Don't listen for the pinch gesture if we are already animating from a previous one.
-            return false;
-        }
-        if (mLauncher.isWorkspaceLocked()) {
-            // Don't listen for the pinch gesture if the workspace isn't ready.
-            return false;
-        }
-        if (mWorkspace == null) {
-            mWorkspace = mLauncher.getWorkspace();
-            mThresholdManager = new PinchThresholdManager(mWorkspace);
-            mAnimationManager = new PinchAnimationManager(mLauncher);
-        }
-        if (mWorkspace.isSwitchingState() || mWorkspace.mScrollInteractionBegan) {
-            // Don't listen for the pinch gesture while switching state, as it will cause a jump
-            // once the state switching animation is complete.
-            return false;
-        }
-        if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
-            // Don't listen for the pinch gesture if a floating view is open.
-            return false;
-        }
-
-        mPreviousProgress = mWorkspace.isInOverviewMode() ? OVERVIEW_PROGRESS : WORKSPACE_PROGRESS;
-        mPreviousTimeMillis = System.currentTimeMillis();
-        mInterpolator = mWorkspace.isInOverviewMode() ? new LogDecelerateInterpolator(100, 0)
-                : new LogAccelerateInterpolator(100, 0);
-        mPinchStarted = true;
-        mWorkspace.onPrepareStateTransition(true);
-        return true;
-    }
-
-    @Override
-    public void onScaleEnd(ScaleGestureDetector detector) {
-        super.onScaleEnd(detector);
-
-        float progressVelocity = mProgressDelta / mTimeDelta;
-        float passedThreshold = mThresholdManager.getPassedThreshold();
-        boolean isFling = mWorkspace.isInOverviewMode() && progressVelocity >= FLING_VELOCITY
-                || !mWorkspace.isInOverviewMode() && progressVelocity <= -FLING_VELOCITY;
-        boolean shouldCancelPinch = !isFling && passedThreshold < PinchThresholdManager.THRESHOLD_ONE;
-        // If we are going towards overview, mPreviousProgress is how much further we need to
-        // go, since it is going from 1 to 0. If we are going to workspace, we want
-        // 1 - mPreviousProgress.
-        float remainingProgress = mPreviousProgress;
-        if (mWorkspace.isInOverviewMode() || shouldCancelPinch) {
-            remainingProgress = 1f - mPreviousProgress;
-        }
-        int duration = computeDuration(remainingProgress, progressVelocity);
-        if (shouldCancelPinch) {
-            cancelPinch(mPreviousProgress, duration);
-        } else if (passedThreshold < PinchThresholdManager.THRESHOLD_THREE) {
-            float toProgress = mWorkspace.isInOverviewMode() ?
-                    WORKSPACE_PROGRESS : OVERVIEW_PROGRESS;
-            mAnimationManager.animateToProgress(mPreviousProgress, toProgress, duration,
-                    mThresholdManager);
-        } else {
-            mThresholdManager.reset();
-            mWorkspace.onEndStateTransition();
-        }
-        mPinchStarted = false;
-        mPinchCanceled = false;
-    }
-
-    /**
-     * Compute the amount of time required to complete the transition based on the current pinch
-     * speed. If this time is too long, instead return the normal duration, ignoring the speed.
-     */
-    private int computeDuration(float remainingProgress, float progressVelocity) {
-        float progressSpeed = Math.abs(progressVelocity);
-        int remainingMillis = (int) (remainingProgress / progressSpeed);
-        return Math.min(remainingMillis, mAnimationManager.getNormalOverviewTransitionDuration());
-    }
-
-    /**
-     * Cancels the current pinch, returning back to where the pinch started (either workspace or
-     * overview). If duration is -1, the default overview transition duration is used.
-     */
-    private void cancelPinch(float currentProgress, int duration) {
-        if (mPinchCanceled) return;
-        mPinchCanceled = true;
-        float toProgress = mWorkspace.isInOverviewMode() ? OVERVIEW_PROGRESS : WORKSPACE_PROGRESS;
-        mAnimationManager.animateToProgress(currentProgress, toProgress, duration,
-                mThresholdManager);
-        mPinchStarted = false;
-    }
-
-    @Override
-    public boolean onScale(ScaleGestureDetector detector) {
-        if (mThresholdManager.getPassedThreshold() == PinchThresholdManager.THRESHOLD_THREE) {
-            // We completed the pinch, so stop listening to further movement until user lets go.
-            return true;
-        }
-        if (mLauncher.getDragController().isDragging()) {
-            mLauncher.getDragController().cancelDrag();
-        }
-
-        float pinchDist = detector.getCurrentSpan() - detector.getPreviousSpan();
-        if (pinchDist < 0 && mWorkspace.isInOverviewMode() ||
-                pinchDist > 0 && !mWorkspace.isInOverviewMode()) {
-            // Pinching the wrong way, so ignore.
-            return false;
-        }
-        // Pinch distance must equal the workspace width before switching states.
-        int pinchDistanceToCompleteTransition = mWorkspace.getWidth();
-        float overviewScale = mWorkspace.getOverviewModeShrinkFactor();
-        float initialWorkspaceScale = mWorkspace.isInOverviewMode() ? overviewScale : 1f;
-        float pinchScale = initialWorkspaceScale + pinchDist / pinchDistanceToCompleteTransition;
-        // Bound the scale between the overview scale and the normal workspace scale (1f).
-        pinchScale = Math.max(overviewScale, Math.min(pinchScale, 1f));
-        // Progress ranges from 0 to 1, where 0 corresponds to the overview scale and 1
-        // corresponds to the normal workspace scale (1f).
-        float progress = (pinchScale - overviewScale) / (1f - overviewScale);
-        float interpolatedProgress = mInterpolator.getInterpolation(progress);
-
-        mAnimationManager.setAnimationProgress(interpolatedProgress);
-        float passedThreshold = mThresholdManager.updateAndAnimatePassedThreshold(
-                interpolatedProgress, mAnimationManager);
-        if (passedThreshold == PinchThresholdManager.THRESHOLD_THREE) {
-            return true;
-        }
-
-        mProgressDelta = interpolatedProgress - mPreviousProgress;
-        mPreviousProgress = interpolatedProgress;
-        mTimeDelta = System.currentTimeMillis() - mPreviousTimeMillis;
-        mPreviousTimeMillis = System.currentTimeMillis();
-        return false;
-    }
-}
\ No newline at end of file
diff --git a/src/com/android/launcher3/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
index a7e68ff..841c0cd 100644
--- a/src/com/android/launcher3/ShortcutAndWidgetContainer.java
+++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
@@ -108,28 +108,21 @@
 
     public void measureChild(View child) {
         CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
-        if (!lp.isFullscreen) {
-            final DeviceProfile profile = mLauncher.getDeviceProfile();
+        final DeviceProfile profile = mLauncher.getDeviceProfile();
 
-            if (child instanceof LauncherAppWidgetHostView) {
-                lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX,
-                        profile.appWidgetScale.x, profile.appWidgetScale.y);
-                // Widgets have their own padding
-            } else {
-                lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX);
-                // Center the icon/folder
-                int cHeight = getCellContentHeight();
-                int cellPaddingY = (int) Math.max(0, ((lp.height - cHeight) / 2f));
-                int cellPaddingX = mContainerType == CellLayout.WORKSPACE
-                        ? profile.workspaceCellPaddingXPx
-                        : (int) (profile.edgeMarginPx / 2f);
-                child.setPadding(cellPaddingX, cellPaddingY, cellPaddingX, 0);
-            }
+        if (child instanceof LauncherAppWidgetHostView) {
+            lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX,
+                    profile.appWidgetScale.x, profile.appWidgetScale.y);
+            // Widgets have their own padding
         } else {
-            lp.x = 0;
-            lp.y = 0;
-            lp.width = getMeasuredWidth();
-            lp.height = getMeasuredHeight();
+            lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX);
+            // Center the icon/folder
+            int cHeight = getCellContentHeight();
+            int cellPaddingY = (int) Math.max(0, ((lp.height - cHeight) / 2f));
+            int cellPaddingX = mContainerType == CellLayout.WORKSPACE
+                    ? profile.workspaceCellPaddingXPx
+                    : (int) (profile.edgeMarginPx / 2f);
+            child.setPadding(cellPaddingX, cellPaddingY, cellPaddingX, 0);
         }
         int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
         int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java
index adf008b..ec608ca 100644
--- a/src/com/android/launcher3/ShortcutInfo.java
+++ b/src/com/android/launcher3/ShortcutInfo.java
@@ -65,13 +65,6 @@
     public static final int FLAG_SUPPORTS_WEB_UI = 16; //0B10000;
 
     /**
-     * Indicates if it represents a common type mentioned in {@link CommonAppTypeParser}.
-     * Upto 15 different types supported.
-     */
-    @Deprecated
-    public static final int FLAG_RESTORED_APP_TYPE = 0B0011110000;
-
-    /**
      * The intent used to start the application.
      */
     public Intent intent;
@@ -83,42 +76,6 @@
     public Intent.ShortcutIconResource iconResource;
 
     /**
-     * Indicates that the icon is disabled due to safe mode restrictions.
-     */
-    public static final int FLAG_DISABLED_SAFEMODE = 1 << 0;
-
-    /**
-     * Indicates that the icon is disabled as the app is not available.
-     */
-    public static final int FLAG_DISABLED_NOT_AVAILABLE = 1 << 1;
-
-    /**
-     * Indicates that the icon is disabled as the app is suspended
-     */
-    public static final int FLAG_DISABLED_SUSPENDED = 1 << 2;
-
-    /**
-     * Indicates that the icon is disabled as the user is in quiet mode.
-     */
-    public static final int FLAG_DISABLED_QUIET_USER = 1 << 3;
-
-    /**
-     * Indicates that the icon is disabled as the publisher has disabled the actual shortcut.
-     */
-    public static final int FLAG_DISABLED_BY_PUBLISHER = 1 << 4;
-
-    /**
-     * Indicates that the icon is disabled as the user partition is currently locked.
-     */
-    public static final int FLAG_DISABLED_LOCKED_USER = 1 << 5;
-
-    /**
-     * Could be disabled, if the the app is installed but unavailable (eg. in safe mode or when
-     * sd-card is not available).
-     */
-    public int isDisabled = DEFAULT;
-
-    /**
      * A message to display when the user tries to start a disabled shortcut.
      * This is currently only used for deep shortcuts.
      */
@@ -142,7 +99,6 @@
         iconResource = info.iconResource;
         status = info.status;
         mInstallProgress = info.mInstallProgress;
-        isDisabled = info.isDisabled;
     }
 
     /** TODO: Remove this.  It's only called by ApplicationInfo.makeShortcut. */
@@ -150,7 +106,6 @@
         super(info);
         title = Utilities.trim(info.title);
         intent = new Intent(info.intent);
-        isDisabled = info.isDisabled;
     }
 
     /**
@@ -219,9 +174,9 @@
         contentDescription = UserManagerCompat.getInstance(context)
                 .getBadgedLabelForUser(label, user);
         if (shortcutInfo.isEnabled()) {
-            isDisabled &= ~FLAG_DISABLED_BY_PUBLISHER;
+            runtimeStatusFlags &= ~FLAG_DISABLED_BY_PUBLISHER;
         } else {
-            isDisabled |= FLAG_DISABLED_BY_PUBLISHER;
+            runtimeStatusFlags |= FLAG_DISABLED_BY_PUBLISHER;
         }
         disabledMessage = shortcutInfo.getDisabledMessage();
     }
@@ -233,11 +188,6 @@
     }
 
     @Override
-    public boolean isDisabled() {
-        return isDisabled != 0;
-    }
-
-    @Override
     public ComponentName getTargetComponent() {
         ComponentName cn = super.getTargetComponent();
         if (cn == null && (itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT
diff --git a/src/com/android/launcher3/UninstallDropTarget.java b/src/com/android/launcher3/UninstallDropTarget.java
index 902fd34..68a441a 100644
--- a/src/com/android/launcher3/UninstallDropTarget.java
+++ b/src/com/android/launcher3/UninstallDropTarget.java
@@ -1,5 +1,8 @@
 package com.android.launcher3;
 
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_SYSTEM_MASK;
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_SYSTEM_NO;
+
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -10,18 +13,28 @@
 import android.os.Bundle;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.util.ArrayMap;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.view.View;
 import android.widget.Toast;
 
+import com.android.launcher3.Launcher.OnResumeCallback;
+import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
 import com.android.launcher3.compat.LauncherAppsCompat;
+import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 
 import java.net.URISyntaxException;
 
-public class UninstallDropTarget extends ButtonDropTarget {
+public class UninstallDropTarget extends ButtonDropTarget implements OnAlarmListener {
 
     private static final String TAG = "UninstallDropTarget";
-    private static Boolean sUninstallDisabled;
+
+    private static final long CACHE_EXPIRE_TIMEOUT = 5000;
+    private final ArrayMap<UserHandle, Boolean> mUninstallDisabledCache = new ArrayMap<>(1);
+
+    private final Alarm mCacheExpireAlarm;
 
     public UninstallDropTarget(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
@@ -29,6 +42,9 @@
 
     public UninstallDropTarget(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
+
+        mCacheExpireAlarm = new Alarm();
+        mCacheExpireAlarm.setOnAlarmListener(this);
     }
 
     @Override
@@ -44,34 +60,45 @@
     }
 
     @Override
-    protected boolean supportsDrop(DragSource source, ItemInfo info) {
-        return supportsDrop(getContext(), info);
+    public void onAlarm(Alarm alarm) {
+        mUninstallDisabledCache.clear();
     }
 
-    public static boolean supportsDrop(Context context, ItemInfo info) {
-        if (sUninstallDisabled == null) {
-            UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
-            Bundle restrictions = userManager.getUserRestrictions();
-            sUninstallDisabled = restrictions.getBoolean(UserManager.DISALLOW_APPS_CONTROL, false)
+    @Override
+    public int getAccessibilityAction() {
+        return LauncherAccessibilityDelegate.UNINSTALL;
+    }
+
+    @Override
+    protected boolean supportsDrop(ItemInfo info) {
+        Boolean uninstallDisabled = mUninstallDisabledCache.get(info.user);
+        if (uninstallDisabled == null) {
+            UserManager userManager =
+                    (UserManager) getContext().getSystemService(Context.USER_SERVICE);
+            Bundle restrictions = userManager.getUserRestrictions(info.user);
+            uninstallDisabled = restrictions.getBoolean(UserManager.DISALLOW_APPS_CONTROL, false)
                     || restrictions.getBoolean(UserManager.DISALLOW_UNINSTALL_APPS, false);
+            mUninstallDisabledCache.put(info.user, uninstallDisabled);
         }
-        if (sUninstallDisabled) {
+        // Cancel any pending alarm and set cache expiry after some time
+        mCacheExpireAlarm.setAlarm(CACHE_EXPIRE_TIMEOUT);
+        if (uninstallDisabled) {
             return false;
         }
 
-        if (info instanceof AppInfo) {
-            AppInfo appInfo = (AppInfo) info;
-            if (appInfo.isSystemApp != AppInfo.FLAG_SYSTEM_UNKNOWN) {
-                return (appInfo.isSystemApp & AppInfo.FLAG_SYSTEM_NO) != 0;
+        if (info instanceof ItemInfoWithIcon) {
+            ItemInfoWithIcon iconInfo = (ItemInfoWithIcon) info;
+            if ((iconInfo.runtimeStatusFlags & FLAG_SYSTEM_MASK) != 0) {
+                return (iconInfo.runtimeStatusFlags & FLAG_SYSTEM_NO) != 0;
             }
         }
-        return getUninstallTarget(context, info) != null;
+        return getUninstallTarget(info) != null;
     }
 
     /**
      * @return the component name that should be uninstalled or null.
      */
-    private static ComponentName getUninstallTarget(Context context, ItemInfo item) {
+    private ComponentName getUninstallTarget(ItemInfo item) {
         Intent intent = null;
         UserHandle user = null;
         if (item != null &&
@@ -80,7 +107,7 @@
             user = item.user;
         }
         if (intent != null) {
-            LauncherActivityInfo info = LauncherAppsCompat.getInstance(context)
+            LauncherActivityInfo info = LauncherAppsCompat.getInstance(mLauncher)
                     .resolveActivity(intent, user);
             if (info != null
                     && (info.getApplicationInfo().flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
@@ -91,98 +118,101 @@
     }
 
     @Override
-    public void onDrop(DragObject d) {
-        // Differ item deletion
-        if (d.dragSource instanceof DropTargetSource) {
-            ((DropTargetSource) d.dragSource).deferCompleteDropAfterUninstallActivity();
-        }
-        super.onDrop(d);
+    public void onDrop(DragObject d, DragOptions options) {
+        // Defer onComplete
+        d.dragSource = new DeferredOnComplete(d.dragSource, getContext());
+        super.onDrop(d, options);
     }
 
     @Override
     public void completeDrop(final DragObject d) {
-        DropTargetResultCallback callback = d.dragSource instanceof DropTargetResultCallback
-                ? (DropTargetResultCallback) d.dragSource : null;
-        startUninstallActivity(mLauncher, d.dragInfo, callback);
+        ComponentName target = performDropAction(d.dragInfo);
+        if (d.dragSource instanceof DeferredOnComplete) {
+            DeferredOnComplete deferred = (DeferredOnComplete) d.dragSource;
+            if (target != null) {
+                deferred.mPackageName = target.getPackageName();
+                mLauncher.setOnResumeCallback(deferred);
+            } else {
+                deferred.sendFailure();
+            }
+        }
     }
 
-    public static boolean startUninstallActivity(Launcher launcher, ItemInfo info) {
-        return startUninstallActivity(launcher, info, null);
-    }
-
-    public static boolean startUninstallActivity(
-            final Launcher launcher, ItemInfo info, DropTargetResultCallback callback) {
-        final ComponentName cn = getUninstallTarget(launcher, info);
-
-        boolean canUninstall;
+    /**
+     * Performs the drop action and returns the target component for the dragObject or null if
+     * the action was not performed.
+     */
+    protected ComponentName performDropAction(ItemInfo info) {
+        ComponentName cn = getUninstallTarget(info);
         if (cn == null) {
             // System applications cannot be installed. For now, show a toast explaining that.
             // We may give them the option of disabling apps this way.
-            Toast.makeText(launcher, R.string.uninstall_system_app_text, Toast.LENGTH_SHORT).show();
-            canUninstall = false;
-        } else {
-            try {
-                Intent i = Intent.parseUri(launcher.getString(R.string.delete_package_intent), 0)
-                        .setData(Uri.fromParts("package", cn.getPackageName(), cn.getClassName()))
-                        .putExtra(Intent.EXTRA_USER, info.user);
-                launcher.startActivity(i);
-                canUninstall = true;
-            } catch (URISyntaxException e) {
-                Log.e(TAG, "Failed to parse intent to start uninstall activity for item=" + info);
-                canUninstall = false;
+            Toast.makeText(mLauncher, R.string.uninstall_system_app_text, Toast.LENGTH_SHORT).show();
+            return null;
+        }
+        try {
+            Intent i = Intent.parseUri(mLauncher.getString(R.string.delete_package_intent), 0)
+                    .setData(Uri.fromParts("package", cn.getPackageName(), cn.getClassName()))
+                    .putExtra(Intent.EXTRA_USER, info.user);
+            mLauncher.startActivity(i);
+            return cn;
+        } catch (URISyntaxException e) {
+            Log.e(TAG, "Failed to parse intent to start uninstall activity for item=" + info);
+            return null;
+        }
+    }
+
+    @Override
+    public void onAccessibilityDrop(View view, ItemInfo item) {
+        performDropAction(item);
+    }
+
+    /**
+     * A wrapper around {@link DragSource} which delays the {@link #onDropCompleted} action until
+     * {@link #onLauncherResume}
+     */
+    private class DeferredOnComplete implements DragSource, OnResumeCallback {
+
+        private final DragSource mOriginal;
+        private final Context mContext;
+
+        private String mPackageName;
+        private DragObject mDragObject;
+
+        public DeferredOnComplete(DragSource original, Context context) {
+            mOriginal = original;
+            mContext = context;
+        }
+
+        @Override
+        public void onDropCompleted(View target, DragObject d,
+                boolean success) {
+            mDragObject = d;
+        }
+
+        @Override
+        public void fillInLogContainerData(View v, ItemInfo info, Target target,
+                Target targetParent) {
+            mOriginal.fillInLogContainerData(v, info, target, targetParent);
+        }
+
+        @Override
+        public void onLauncherResume() {
+            // We use MATCH_UNINSTALLED_PACKAGES as the app can be on SD card as well.
+            if (LauncherAppsCompat.getInstance(mContext)
+                    .getApplicationInfo(mPackageName, PackageManager.MATCH_UNINSTALLED_PACKAGES,
+                            mDragObject.dragInfo.user) == null) {
+                mDragObject.dragSource = mOriginal;
+                mOriginal.onDropCompleted(UninstallDropTarget.this, mDragObject, true);
+            } else {
+                sendFailure();
             }
         }
-        if (callback != null) {
-            sendUninstallResult(launcher, canUninstall, cn, info.user, callback);
+
+        public void sendFailure() {
+            mDragObject.dragSource = mOriginal;
+            mDragObject.cancelled = true;
+            mOriginal.onDropCompleted(UninstallDropTarget.this, mDragObject, false);
         }
-        return canUninstall;
-    }
-
-    /**
-     * Notifies the {@param callback} whether the uninstall was successful or not.
-     *
-     * Since there is no direct callback for an uninstall request, we check the package existence
-     * when the launch resumes next time. This assumes that the uninstall activity will finish only
-     * after the task is completed
-     */
-    protected static void sendUninstallResult(
-            final Launcher launcher, boolean activityStarted,
-            final ComponentName cn, final UserHandle user,
-            final DropTargetResultCallback callback) {
-        if (activityStarted)  {
-            final Runnable checkIfUninstallWasSuccess = new Runnable() {
-                @Override
-                public void run() {
-                    // We use MATCH_UNINSTALLED_PACKAGES as the app can be on SD card as well.
-                    boolean uninstallSuccessful = LauncherAppsCompat.getInstance(launcher)
-                            .getApplicationInfo(cn.getPackageName(),
-                                    PackageManager.MATCH_UNINSTALLED_PACKAGES, user) == null;
-                    callback.onDragObjectRemoved(uninstallSuccessful);
-                }
-            };
-            launcher.addOnResumeCallback(checkIfUninstallWasSuccess);
-        } else {
-            callback.onDragObjectRemoved(false);
-        }
-    }
-
-    public interface DropTargetResultCallback {
-        /**
-         * A drag operation was complete.
-         * @param isRemoved true if the drag object should be removed, false otherwise.
-         */
-        void onDragObjectRemoved(boolean isRemoved);
-    }
-
-    /**
-     * Interface defining an object that can provide uninstallable drag objects.
-     */
-    public interface DropTargetSource extends DropTargetResultCallback {
-
-        /**
-         * Indicates that an uninstall request are made and the actual result may come
-         * after some time.
-         */
-        void deferCompleteDropAfterUninstallActivity();
     }
 }
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index b6876f6..e6bc770 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -309,6 +309,9 @@
         float highScore = -1;
         int bestHue = -1;
 
+        int[] pixels = new int[samples];
+        int pixelCount = 0;
+
         for (int y = 0; y < height; y += sampleStride) {
             for (int x = 0; x < width; x += sampleStride) {
                 int argb = bitmap.getPixel(x, y);
@@ -326,6 +329,9 @@
                     // Defensively avoid array bounds violations.
                     continue;
                 }
+                if (pixelCount < samples) {
+                    pixels[pixelCount++] = rgb;
+                }
                 float score = hsv[1] * hsv[2];
                 hueScoreHistogram[hue] += score;
                 if (hueScoreHistogram[hue] > highScore) {
@@ -335,31 +341,29 @@
             }
         }
 
-        SparseArray<Float> rgbScores = new SparseArray<Float>();
+        SparseArray<Float> rgbScores = new SparseArray<>();
         int bestColor = 0xff000000;
         highScore = -1;
         // Go back over the RGB colors that match the winning hue,
         // creating a histogram of weighted s*v scores, for up to 100*100 [s,v] buckets.
         // The highest-scoring RGB color wins.
-        for (int y = 0; y < height; y += sampleStride) {
-            for (int x = 0; x < width; x += sampleStride) {
-                int rgb = bitmap.getPixel(x, y) | 0xff000000;
-                Color.colorToHSV(rgb, hsv);
-                int hue = (int) hsv[0];
-                if (hue == bestHue) {
-                    float s = hsv[1];
-                    float v = hsv[2];
-                    int bucket = (int) (s * 100) + (int) (v * 10000);
-                    // Score by cumulative saturation * value.
-                    float score = s * v;
-                    Float oldTotal = rgbScores.get(bucket);
-                    float newTotal = oldTotal == null ? score : oldTotal + score;
-                    rgbScores.put(bucket, newTotal);
-                    if (newTotal > highScore) {
-                        highScore = newTotal;
-                        // All the colors in the winning bucket are very similar. Last in wins.
-                        bestColor = rgb;
-                    }
+        for (int i = 0; i < pixelCount; i++) {
+            int rgb = pixels[i];
+            Color.colorToHSV(rgb, hsv);
+            int hue = (int) hsv[0];
+            if (hue == bestHue) {
+                float s = hsv[1];
+                float v = hsv[2];
+                int bucket = (int) (s * 100) + (int) (v * 10000);
+                // Score by cumulative saturation * value.
+                float score = s * v;
+                Float oldTotal = rgbScores.get(bucket);
+                float newTotal = oldTotal == null ? score : oldTotal + score;
+                rgbScores.put(bucket, newTotal);
+                if (newTotal > highScore) {
+                    highScore = newTotal;
+                    // All the colors in the winning bucket are very similar. Last in wins.
+                    bestColor = rgb;
                 }
             }
         }
@@ -610,6 +614,12 @@
         return c == null || c.isEmpty();
     }
 
+    public static boolean isAccessibilityEnabled(Context context) {
+        AccessibilityManager accessibilityManager = (AccessibilityManager)
+                context.getSystemService(Context.ACCESSIBILITY_SERVICE);
+        return accessibilityManager.isEnabled();
+    }
+
     public static void sendCustomAccessibilityEvent(View target, int type, String text) {
         AccessibilityManager accessibilityManager = (AccessibilityManager)
                 target.getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
diff --git a/src/com/android/launcher3/VerticalSwipeController.java b/src/com/android/launcher3/VerticalSwipeController.java
new file mode 100644
index 0000000..b3dc176
--- /dev/null
+++ b/src/com/android/launcher3/VerticalSwipeController.java
@@ -0,0 +1,282 @@
+/*
+ * 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;
+
+import static com.android.launcher3.LauncherState.ALL_APPS;
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
+import static com.android.launcher3.anim.SpringAnimationHandler.Y_DIRECTION;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.support.animation.SpringAnimation;
+import android.util.Log;
+import android.view.MotionEvent;
+
+import com.android.launcher3.allapps.AllAppsContainerView;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.SpringAnimationHandler;
+import com.android.launcher3.touch.SwipeDetector;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.launcher3.util.TouchController;
+
+import java.util.ArrayList;
+
+/**
+ * Handles vertical touch gesture on the DragLayer
+ */
+public class VerticalSwipeController extends AnimatorListenerAdapter
+        implements TouchController, SwipeDetector.Listener {
+
+    private static final String TAG = "VerticalSwipeController";
+
+    private static final float RECATCH_REJECTION_FRACTION = .0875f;
+    private static final int SINGLE_FRAME_MS = 16;
+
+    // Progress after which the transition is assumed to be a success in case user does not fling
+    private static final float SUCCESS_TRANSITION_PROGRESS = 0.5f;
+
+    private final Launcher mLauncher;
+    private final SwipeDetector mDetector;
+
+    private boolean mNoIntercept;
+    private int mStartContainerType;
+
+    private AnimatorPlaybackController mCurrentAnimation;
+    private LauncherState mToState;
+
+    private float mStartProgress;
+    // Ratio of transition process [0, 1] to drag displacement (px)
+    private float mProgressMultiplier;
+
+    private SpringAnimationHandler[] mSpringHandlers;
+
+    public VerticalSwipeController(Launcher l) {
+        mLauncher = l;
+        mDetector = new SwipeDetector(l, this, SwipeDetector.VERTICAL);
+    }
+
+    private boolean canInterceptTouch(MotionEvent ev) {
+        if (!mLauncher.isInState(NORMAL) && !mLauncher.isInState(ALL_APPS)) {
+            // Don't listen for the swipe gesture if we are already in some other state.
+            return false;
+        }
+        if (mCurrentAnimation != null) {
+            // If we are already animating from a previous state, we can intercept.
+            return true;
+        }
+        if (mLauncher.isInState(ALL_APPS) && !mLauncher.getAppsView().shouldContainerScroll(ev)) {
+            return false;
+        }
+        if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public void onAnimationCancel(Animator animation) {
+        if (mCurrentAnimation != null && animation == mCurrentAnimation.getTarget()) {
+            Log.e(TAG, "Who dare cancel the animation when I am in control", new Exception());
+            mDetector.finishedScrolling();
+            mCurrentAnimation = null;
+        }
+    }
+
+    private void initSprings() {
+        AllAppsContainerView appsView = mLauncher.getAppsView();
+
+        SpringAnimationHandler handler = appsView.getSpringAnimationHandler();
+        if (handler == null) {
+            mSpringHandlers = new SpringAnimationHandler[0];
+            return;
+        }
+
+        ArrayList<SpringAnimationHandler> handlers = new ArrayList<>();
+        handlers.add(handler);
+
+        SpringAnimation searchSpring = appsView.getSearchUiManager().getSpringForFling();
+        if (searchSpring != null) {
+            SpringAnimationHandler searchHandler =
+                    new SpringAnimationHandler(Y_DIRECTION, handler.getFactory());
+            searchHandler.add(searchSpring, true /* setDefaultValues */);
+            handlers.add(searchHandler);
+        }
+
+        mSpringHandlers = handlers.toArray(new SpringAnimationHandler[handlers.size()]);
+    }
+
+    @Override
+    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+            mNoIntercept = !canInterceptTouch(ev);
+            if (mNoIntercept) {
+                return false;
+            }
+
+            // Now figure out which direction scroll events the controller will start
+            // calling the callbacks.
+            final int directionsToDetectScroll;
+            boolean ignoreSlopWhenSettling = false;
+
+            if (mCurrentAnimation != null) {
+                if (mCurrentAnimation.getProgressFraction() > 1 - RECATCH_REJECTION_FRACTION) {
+                    directionsToDetectScroll = SwipeDetector.DIRECTION_POSITIVE;
+                } else if (mCurrentAnimation.getProgressFraction() < RECATCH_REJECTION_FRACTION ) {
+                    directionsToDetectScroll = SwipeDetector.DIRECTION_NEGATIVE;
+                } else {
+                    directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH;
+                    ignoreSlopWhenSettling = true;
+                }
+            } else {
+                if (mLauncher.isInState(ALL_APPS)) {
+                    directionsToDetectScroll = SwipeDetector.DIRECTION_NEGATIVE;
+                    mStartContainerType = ContainerType.ALLAPPS;
+                } else {
+                    directionsToDetectScroll = SwipeDetector.DIRECTION_POSITIVE;
+                    mStartContainerType = mLauncher.getDragLayer().isEventOverHotseat(ev) ?
+                            ContainerType.HOTSEAT : ContainerType.WORKSPACE;
+                }
+            }
+
+            mDetector.setDetectableScrollConditions(
+                    directionsToDetectScroll, ignoreSlopWhenSettling);
+
+            if (mSpringHandlers == null) {
+                initSprings();
+            }
+        }
+
+        if (mNoIntercept) {
+            return false;
+        }
+
+        onControllerTouchEvent(ev);
+        return mDetector.isDraggingOrSettling();
+    }
+
+    @Override
+    public boolean onControllerTouchEvent(MotionEvent ev) {
+        for (SpringAnimationHandler h : mSpringHandlers) {
+            h.addMovement(ev);
+        }
+        return mDetector.onTouchEvent(ev);
+    }
+
+    @Override
+    public void onDragStart(boolean start) {
+        if (mCurrentAnimation == null) {
+            float range = getShiftRange();
+            long maxAccuracy = (long) (2 * range);
+
+            // Build current animation
+            mToState = mLauncher.isInState(ALL_APPS) ? NORMAL : ALL_APPS;
+            mCurrentAnimation = mLauncher.getStateManager()
+                    .createAnimationToNewWorkspace(mToState, maxAccuracy);
+            mCurrentAnimation.getTarget().addListener(this);
+            mStartProgress = 0;
+            mProgressMultiplier = (mLauncher.isInState(ALL_APPS) ? 1 : -1) / range;
+            mCurrentAnimation.dispatchOnStart();
+        } else {
+            mCurrentAnimation.pause();
+            mStartProgress = mCurrentAnimation.getProgressFraction();
+        }
+
+        for (SpringAnimationHandler h : mSpringHandlers) {
+            h.skipToEnd();
+        }
+    }
+
+    private float getShiftRange() {
+        return mLauncher.mAllAppsController.getShiftRange();
+    }
+
+    @Override
+    public boolean onDrag(float displacement, float velocity) {
+        float deltaProgress = mProgressMultiplier * displacement;
+        mCurrentAnimation.setPlayFraction(deltaProgress + mStartProgress);
+        return true;
+    }
+
+    @Override
+    public void onDragEnd(float velocity, boolean fling) {
+        final long animationDuration;
+        final int logAction;
+        final LauncherState targetState;
+        final float progress = mCurrentAnimation.getProgressFraction();
+
+        if (fling) {
+            logAction = Touch.FLING;
+            if (velocity < 0) {
+                targetState = ALL_APPS;
+                animationDuration = SwipeDetector.calculateDuration(velocity,
+                        mToState == ALL_APPS ? (1 - progress) : progress);
+            } else {
+                targetState = NORMAL;
+                animationDuration = SwipeDetector.calculateDuration(velocity,
+                        mToState == ALL_APPS ? progress : (1 - progress));
+            }
+            // snap to top or bottom using the release velocity
+        } else {
+            logAction = Touch.SWIPE;
+            if (progress > SUCCESS_TRANSITION_PROGRESS) {
+                targetState = mToState;
+                animationDuration = SwipeDetector.calculateDuration(velocity, 1 - progress);
+            } else {
+                targetState = mToState == ALL_APPS ? NORMAL : ALL_APPS;
+                animationDuration = SwipeDetector.calculateDuration(velocity, progress);
+            }
+        }
+
+        if (fling && targetState == ALL_APPS) {
+            for (SpringAnimationHandler h : mSpringHandlers) {
+                // The icons are moving upwards, so we go to 0 from 1. (y-axis 1 is below 0.)
+                h.animateToFinalPosition(0 /* pos */, 1 /* startValue */);
+            }
+        }
+
+        mCurrentAnimation.setEndAction(new Runnable() {
+            @Override
+            public void run() {
+                if (targetState == mToState) {
+                    // Transition complete. log the action
+                    mLauncher.getUserEventDispatcher().logActionOnContainer(logAction,
+                            mToState == ALL_APPS ? Direction.UP : Direction.DOWN,
+                            mStartContainerType, mLauncher.getWorkspace().getCurrentPage());
+                } else {
+                    mLauncher.getStateManager().goToState(
+                            mToState == ALL_APPS ? NORMAL : ALL_APPS, false);
+                }
+                mDetector.finishedScrolling();
+                mCurrentAnimation = null;
+            }
+        });
+
+        float nextFrameProgress = Utilities.boundToRange(
+                progress + velocity * SINGLE_FRAME_MS / getShiftRange(), 0f, 1f);
+
+        ValueAnimator anim = mCurrentAnimation.getAnimationPlayer();
+        anim.setFloatValues(nextFrameProgress, targetState == mToState ? 1f : 0f);
+        anim.setDuration(animationDuration);
+        anim.setInterpolator(scrollInterpolatorForVelocity(velocity));
+        anim.start();
+    }
+}
diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java
index a65ea9b..bdfeae1 100644
--- a/src/com/android/launcher3/WidgetPreviewLoader.java
+++ b/src/com/android/launcher3/WidgetPreviewLoader.java
@@ -22,7 +22,6 @@
 import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
 import android.os.AsyncTask;
-import android.os.Build;
 import android.os.CancellationSignal;
 import android.os.Handler;
 import android.os.UserHandle;
@@ -93,12 +92,11 @@
      * @return a request id which can be used to cancel the request.
      */
     public CancellationSignal getPreview(WidgetItem item, int previewWidth,
-            int previewHeight, WidgetCell caller, boolean animate) {
+            int previewHeight, WidgetCell caller) {
         String size = previewWidth + "x" + previewHeight;
         WidgetCacheKey key = new WidgetCacheKey(item.componentName, item.user, size);
 
-        PreviewLoadTask task = new PreviewLoadTask(key, item, previewWidth, previewHeight, caller,
-                animate);
+        PreviewLoadTask task = new PreviewLoadTask(key, item, previewWidth, previewHeight, caller);
         task.executeOnExecutor(Utilities.THREAD_POOL_EXECUTOR);
 
         CancellationSignal signal = new CancellationSignal();
@@ -412,7 +410,8 @@
 
             // Draw icon in the center.
             try {
-                Drawable icon = info.getIcon(launcher, mIconCache);
+                Drawable icon =
+                        mIconCache.getFullResIcon(info.provider.getPackageName(), info.icon);
                 if (icon != null) {
                     int appIconSize = launcher.getDeviceProfile().iconSizePx;
                     int iconSize = (int) Math.min(appIconSize * scale,
@@ -527,19 +526,17 @@
         private final int mPreviewHeight;
         private final int mPreviewWidth;
         private final WidgetCell mCaller;
-        private final boolean mAnimatePreviewIn;
         private final BaseActivity mActivity;
         @Thunk long[] mVersions;
         @Thunk Bitmap mBitmapToRecycle;
 
         PreviewLoadTask(WidgetCacheKey key, WidgetItem info, int previewWidth,
-                int previewHeight, WidgetCell caller, boolean animate) {
+                int previewHeight, WidgetCell caller) {
             mKey = key;
             mInfo = info;
             mPreviewHeight = previewHeight;
             mPreviewWidth = previewWidth;
             mCaller = caller;
-            mAnimatePreviewIn = animate;
             mActivity = BaseActivity.fromContext(mCaller.getContext());
             if (DEBUG) {
                 Log.d(TAG, String.format("%s, %s, %d, %d",
@@ -595,7 +592,7 @@
 
         @Override
         protected void onPostExecute(final Bitmap preview) {
-            mCaller.applyPreview(preview, mAnimatePreviewIn);
+            mCaller.applyPreview(preview);
 
             // Write the generated preview to the DB in the worker thread
             if (mVersions != null) {
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 37ee6d7..0db5a16 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -16,6 +16,14 @@
 
 package com.android.launcher3;
 
+import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
+import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
+import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_TRANSITION_MS;
+import static com.android.launcher3.LauncherState.ALL_APPS;
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.SPRING_LOADED;
+import static com.android.launcher3.Utilities.isAccessibilityEnabled;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
@@ -46,22 +54,17 @@
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.ViewDebug;
 import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityManager;
-import android.view.animation.DecelerateInterpolator;
-import android.view.animation.Interpolator;
 import android.widget.Toast;
 
-import com.android.launcher3.Launcher.CustomContentCallbacks;
 import com.android.launcher3.Launcher.LauncherOverlay;
 import com.android.launcher3.LauncherAppWidgetHost.ProviderChangedListener;
-import com.android.launcher3.UninstallDropTarget.DropTargetSource;
+import com.android.launcher3.LauncherStateManager.AnimationConfig;
 import com.android.launcher3.accessibility.AccessibleDragListenerAdapter;
-import com.android.launcher3.accessibility.OverviewAccessibilityDelegate;
 import com.android.launcher3.accessibility.OverviewScreenAccessibilityDelegate;
 import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
 import com.android.launcher3.anim.AnimationLayerSet;
+import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.badge.FolderBadgeInfo;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
@@ -77,6 +80,7 @@
 import com.android.launcher3.graphics.PreloadIconDrawable;
 import com.android.launcher3.popup.PopupContainerWithArrow;
 import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
+import com.android.launcher3.uioverrides.UiFactory;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
@@ -84,7 +88,6 @@
 import com.android.launcher3.util.LongArrayMap;
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.Thunk;
-import com.android.launcher3.util.VerticalFlingDetector;
 import com.android.launcher3.util.WallpaperOffsetInterpolator;
 import com.android.launcher3.widget.PendingAddShortcutInfo;
 import com.android.launcher3.widget.PendingAddWidgetInfo;
@@ -101,7 +104,7 @@
 public class Workspace extends PagedView
         implements DropTarget, DragSource, View.OnTouchListener,
         DragController.DragListener, ViewGroup.OnHierarchyChangeListener,
-        Insettable, DropTargetSource {
+        Insettable, LauncherStateManager.StateHandler {
     private static final String TAG = "Launcher.Workspace";
 
     /** The value that {@link #mTransitionProgress} must be greater than for
@@ -119,6 +122,8 @@
 
     private static final int ADJACENT_SCREEN_DROP_DURATION = 300;
 
+    private static final int DEFAULT_PAGE = 0;
+
     private static final boolean MAP_NO_RECURSE = false;
     private static final boolean MAP_RECURSE = true;
 
@@ -127,12 +132,6 @@
     // The is the first screen. It is always present, even if its empty.
     public static final long FIRST_SCREEN_ID = 0;
 
-    private final static long CUSTOM_CONTENT_SCREEN_ID = -301;
-
-    private static final long CUSTOM_CONTENT_GESTURE_DELAY = 200;
-    private long mTouchDownTime = -1;
-    private long mCustomContentShowTime = -1;
-
     private LayoutTransition mLayoutTransition;
     @Thunk final WallpaperManager mWallpaperManager;
 
@@ -156,11 +155,6 @@
     private int mDragOverX = -1;
     private int mDragOverY = -1;
 
-    CustomContentCallbacks mCustomContentCallbacks;
-    boolean mCustomContentShowing;
-    private float mLastCustomContentScrollProgress = -1f;
-    private String mCustomContentDescription = "";
-
     /**
      * The CellLayout that is currently being dragged over
      */
@@ -178,37 +172,11 @@
     @Thunk final Launcher mLauncher;
     @Thunk DragController mDragController;
 
-    // These are temporary variables to prevent having to allocate a new object just to
-    // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
-    private static final Rect sTempRect = new Rect();
-
     private final int[] mTempXY = new int[2];
     @Thunk float[] mDragViewVisualCenter = new float[2];
     private final float[] mTempTouchCoordinates = new float[2];
 
     private SpringLoadedDragController mSpringLoadedDragController;
-    private final float mOverviewModeShrinkFactor;
-
-    // State variable that indicates whether the pages are small (ie when you're
-    // in all apps or customize mode)
-
-    public enum State {
-        NORMAL          (false, false, ContainerType.WORKSPACE),
-        NORMAL_HIDDEN   (false, false, ContainerType.ALLAPPS),
-        SPRING_LOADED   (false, true, ContainerType.WORKSPACE),
-        OVERVIEW        (true, true, ContainerType.OVERVIEW),
-        OVERVIEW_HIDDEN (true, false, ContainerType.WIDGETS);
-
-        public final boolean shouldUpdateWidget;
-        public final boolean hasMultipleVisiblePages;
-        public final int containerType;
-
-        State(boolean shouldUpdateWidget, boolean hasMultipleVisiblePages, int containerType) {
-            this.shouldUpdateWidget = shouldUpdateWidget;
-            this.hasMultipleVisiblePages = hasMultipleVisiblePages;
-            this.containerType = containerType;
-        }
-    }
 
     // Direction used for moving the workspace and hotseat UI
     public enum Direction {
@@ -235,11 +203,8 @@
      */
     private final float[] mHotseatAlpha = new float[] {1, 1, 1};
 
-    @ViewDebug.ExportedProperty(category = "launcher")
-    private State mState = State.NORMAL;
     private boolean mIsSwitchingState = false;
 
-    boolean mAnimatingViewIntoPlace = false;
     boolean mChildrenLayersEnabled = true;
 
     private boolean mStripScreensOnPageStopMoving = false;
@@ -250,9 +215,6 @@
     final WallpaperOffsetInterpolator mWallpaperOffset;
     private boolean mUnlockWallpaperFromDefaultPageOnLayout;
 
-    @Thunk Runnable mDelayedResizeRunnable;
-    private Runnable mDelayedSnapToPageRunnable;
-
     // Variables relating to the creation of user folders by hovering shortcuts over shortcuts
     private static final int FOLDER_CREATION_TIMEOUT = 0;
     public static final int REORDER_TIMEOUT = 350;
@@ -264,8 +226,6 @@
     private boolean mAddToExistingFolderOnDrop = false;
     private float mMaxDistanceForFolderCreation;
 
-    private final Canvas mCanvas = new Canvas();
-
     // Variables relating to touch disambiguation (scrolling workspace vs. scrolling a widget)
     private float mXDown;
     private float mYDown;
@@ -295,10 +255,6 @@
     private float mCurrentScale;
     private float mTransitionProgress;
 
-    @Thunk Runnable mDeferredAction;
-    private boolean mDeferDropAfterUninstall;
-    private boolean mUninstallSuccessful;
-
     // State related to Launcher Overlay
     LauncherOverlay mLauncherOverlay;
     boolean mScrollInteractionBegan;
@@ -307,6 +263,8 @@
     boolean mOverlayShown = false;
 
     private boolean mForceDrawAdjacentPages = false;
+    private boolean mPageRearrangeEnabled = false;
+
     // Total over scrollX in the overlay direction.
     private float mOverlayTranslation;
 
@@ -337,14 +295,11 @@
 
         mLauncher = Launcher.getLauncher(context);
         mStateTransitionAnimation = new WorkspaceStateTransitionAnimation(mLauncher, this);
-        final Resources res = getResources();
         DeviceProfile grid = mLauncher.getDeviceProfile();
         mWorkspaceFadeInAdjacentScreens = grid.shouldFadeAdjacentWorkspaceScreens();
         mWallpaperManager = WallpaperManager.getInstance(context);
 
         mWallpaperOffset = new WallpaperOffsetInterpolator(this);
-        mOverviewModeShrinkFactor =
-                res.getInteger(R.integer.config_workspaceOverviewShrinkPercentage) / 100f;
 
         setOnHierarchyChangeListener(this);
         setHapticFeedbackEnabled(false);
@@ -358,29 +313,18 @@
     @Override
     public void setInsets(Rect insets) {
         mInsets.set(insets);
-
-        CellLayout customScreen = getScreenWithId(CUSTOM_CONTENT_SCREEN_ID);
-        if (customScreen != null) {
-            View customContent = customScreen.getShortcutsAndWidgets().getChildAt(0);
-            if (customContent instanceof Insettable) {
-                ((Insettable) customContent).setInsets(mInsets);
-            }
-        }
     }
 
     /**
      * Estimates the size of an item using spans: hSpan, vSpan.
      *
-     * @param springLoaded True if we are in spring loaded mode.
-     * @param unscaledSize True if caller wants to return the unscaled size
      * @return MAX_VALUE for each dimension if unsuccessful.
      */
-    public int[] estimateItemSize(ItemInfo itemInfo, boolean springLoaded, boolean unscaledSize) {
-        float shrinkFactor = mLauncher.getDeviceProfile().workspaceSpringLoadShrinkFactor;
+    public int[] estimateItemSize(ItemInfo itemInfo) {
         int[] size = new int[2];
         if (getChildCount() > 0) {
-            // Use the first non-custom page to estimate the child position
-            CellLayout cl = (CellLayout) getChildAt(numCustomPages());
+            // Use the first page to estimate the child position
+            CellLayout cl = (CellLayout) getChildAt(0);
             boolean isWidget = itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
 
             Rect r = estimateItemPosition(cl, 0, 0, itemInfo.spanX, itemInfo.spanY);
@@ -393,15 +337,10 @@
             size[0] = r.width();
             size[1] = r.height();
 
-            if (isWidget && unscaledSize) {
+            if (isWidget) {
                 size[0] /= scale;
                 size[1] /= scale;
             }
-
-            if (springLoaded) {
-                size[0] *= shrinkFactor;
-                size[1] *= shrinkFactor;
-            }
             return size;
         } else {
             size[0] = Integer.MAX_VALUE;
@@ -428,16 +367,15 @@
         }
 
         if (mOutlineProvider != null) {
-            // The outline is used to visualize where the item will land if dropped
-            mOutlineProvider.generateDragOutline(mCanvas);
+            if (dragObject.dragView != null) {
+                Bitmap preview = dragObject.dragView.getPreviewBitmap();
+
+                // The outline is used to visualize where the item will land if dropped
+                mOutlineProvider.generateDragOutline(preview);
+            }
         }
 
-        updateChildrenLayersEnabled(false);
-        mLauncher.onDragStarted();
-        mLauncher.lockScreenOrientation();
-        mLauncher.onInteractionBegin();
-        // Prevent any Un/InstallShortcutReceivers from updating the db while we are dragging
-        InstallShortcutReceiver.enableInstallQueue(InstallShortcutReceiver.FLAG_DRAG_AND_DROP);
+        updateChildrenLayersEnabled();
 
         // Do not add a new page if it is a accessible drag which was not started by the workspace.
         // We do not support accessibility drag from other sources and instead provide a direct
@@ -469,7 +407,7 @@
         }
 
         // Always enter the spring loaded mode
-        mLauncher.enterSpringLoadedDragMode();
+        mLauncher.getStateManager().goToState(SPRING_LOADED);
     }
 
     public void deferRemoveExtraEmptyScreen() {
@@ -486,32 +424,22 @@
             removeExtraEmptyScreen(true, mDragSourceInternal != null);
         }
 
-        updateChildrenLayersEnabled(false);
-        mLauncher.unlockScreenOrientation(false);
-
-        // Re-enable any Un/InstallShortcutReceiver and now process any queued items
-        InstallShortcutReceiver.disableAndFlushInstallQueue(
-                InstallShortcutReceiver.FLAG_DRAG_AND_DROP, getContext());
-
+        updateChildrenLayersEnabled();
         mOutlineProvider = null;
-        mDragInfo = null;
         mDragSourceInternal = null;
-        mLauncher.onInteractionEnd();
     }
 
     /**
      * Initializes various states for this workspace.
      */
     protected void initWorkspace() {
-        mCurrentPage = getDefaultPage();
+        mCurrentPage = DEFAULT_PAGE;
         DeviceProfile grid = mLauncher.getDeviceProfile();
         setWillNotDraw(false);
         setClipChildren(false);
         setClipToPadding(false);
 
-        setMinScale(mOverviewModeShrinkFactor);
         setupLayoutTransition();
-
         mMaxDistanceForFolderCreation = (0.55f * grid.iconSizePx);
 
         // Set the wallpaper dimensions when Launcher starts up
@@ -521,11 +449,7 @@
     @Override
     public void initParentViews(View parent) {
         super.initParentViews(parent);
-        mPageIndicator.setAccessibilityDelegate(new OverviewAccessibilityDelegate());
-    }
-
-    private int getDefaultPage() {
-        return numCustomPages();
+        mPageIndicator.setAccessibilityDelegate(UiFactory.newPageIndicatorAccessibilityDelegate());
     }
 
     private void setupLayoutTransition() {
@@ -571,33 +495,6 @@
         }
         // Add the first page
         CellLayout firstPage = insertNewWorkspaceScreen(Workspace.FIRST_SCREEN_ID, 0);
-        if (FeatureFlags.PULLDOWN_SEARCH) {
-            firstPage.setOnTouchListener(new VerticalFlingDetector(mLauncher) {
-                // detect fling when touch started from empty space
-                @Override
-                public boolean onTouch(View v, MotionEvent ev) {
-                    if (workspaceInModalState()) return false;
-                    if (shouldConsumeTouch(v)) return true;
-                    if (super.onTouch(v, ev)) {
-                        mLauncher.startSearch("", false, null, false);
-                        return true;
-                    }
-                    return false;
-                }
-            });
-            firstPage.setOnInterceptTouchListener(new VerticalFlingDetector(mLauncher) {
-                // detect fling when touch started from on top of the icons
-                @Override
-                public boolean onTouch(View v, MotionEvent ev) {
-                    if (shouldConsumeTouch(v)) return true;
-                    if (super.onTouch(v, ev)) {
-                        mLauncher.startSearch("", false, null, false);
-                        return true;
-                    }
-                    return false;
-                }
-            });
-        }
         // Always add a QSB on the first screen.
         if (qsb == null) {
             // In transposed layout, we add the QSB in the Grid. As workspace does not touch the
@@ -615,16 +512,9 @@
 
     public void removeAllWorkspaceScreens() {
         // Disable all layout transitions before removing all pages to ensure that we don't get the
-        // transition animations competing with us changing the scroll when we add pages or the
-        // custom content screen
+        // transition animations competing with us changing the scroll when we add pages
         disableLayoutTransitions();
 
-        // Since we increment the current page when we call addCustomContentPage via bindScreens
-        // (and other places), we need to adjust the current page back when we clear the pages
-        if (hasCustomContent()) {
-            removeCustomContentPage();
-        }
-
         // Recycle the QSB widget
         View qsb = findViewById(R.id.search_container_workspace);
         if (qsb != null) {
@@ -677,6 +567,8 @@
         mWorkspaceScreens.put(screenId, newScreen);
         mScreenOrder.add(insertIndex, screenId);
         addView(newScreen, insertIndex);
+        mStateTransitionAnimation.applyChildState(
+                mLauncher.getStateManager().getState(), newScreen, insertIndex);
 
         if (mLauncher.getAccessibilityDelegate().isInAccessibleDrag()) {
             newScreen.enableAccessibleDrag(true, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG);
@@ -685,78 +577,6 @@
         return newScreen;
     }
 
-    public void createCustomContentContainer() {
-        CellLayout customScreen = (CellLayout)
-                LayoutInflater.from(getContext()).inflate(R.layout.workspace_screen, this, false);
-        customScreen.disableDragTarget();
-        customScreen.disableJailContent();
-
-        mWorkspaceScreens.put(CUSTOM_CONTENT_SCREEN_ID, customScreen);
-        mScreenOrder.add(0, CUSTOM_CONTENT_SCREEN_ID);
-
-        // We want no padding on the custom content
-        customScreen.setPadding(0, 0, 0, 0);
-
-        addFullScreenPage(customScreen);
-
-        // Update the custom content hint
-        setCurrentPage(getCurrentPage() + 1);
-    }
-
-    public void removeCustomContentPage() {
-        CellLayout customScreen = getScreenWithId(CUSTOM_CONTENT_SCREEN_ID);
-        if (customScreen == null) {
-            throw new RuntimeException("Expected custom content screen to exist");
-        }
-
-        mWorkspaceScreens.remove(CUSTOM_CONTENT_SCREEN_ID);
-        mScreenOrder.remove(CUSTOM_CONTENT_SCREEN_ID);
-        removeView(customScreen);
-
-        if (mCustomContentCallbacks != null) {
-            mCustomContentCallbacks.onScrollProgressChanged(0);
-            mCustomContentCallbacks.onHide();
-        }
-
-        mCustomContentCallbacks = null;
-
-        // Update the custom content hint
-        setCurrentPage(getCurrentPage() - 1);
-    }
-
-    public void addToCustomContentPage(View customContent, CustomContentCallbacks callbacks,
-            String description) {
-        if (getPageIndexForScreenId(CUSTOM_CONTENT_SCREEN_ID) < 0) {
-            throw new RuntimeException("Expected custom content screen to exist");
-        }
-
-        // Add the custom content to the full screen custom page
-        CellLayout customScreen = getScreenWithId(CUSTOM_CONTENT_SCREEN_ID);
-        int spanX = customScreen.getCountX();
-        int spanY = customScreen.getCountY();
-        CellLayout.LayoutParams lp = new CellLayout.LayoutParams(0, 0, spanX, spanY);
-        lp.canReorder  = false;
-        lp.isFullscreen = true;
-        if (customContent instanceof Insettable) {
-            ((Insettable)customContent).setInsets(mInsets);
-        }
-
-        // Verify that the child is removed from any existing parent.
-        if (customContent.getParent() instanceof ViewGroup) {
-            ViewGroup parent = (ViewGroup) customContent.getParent();
-            parent.removeView(customContent);
-        }
-        customScreen.removeAllViews();
-        customContent.setFocusable(true);
-        customContent.setOnKeyListener(new FullscreenKeyEventListener());
-        customContent.setOnFocusChangeListener(mLauncher.mFocusHandler
-                .getHideIndicatorOnFocusListener());
-        customScreen.addViewToCellLayout(customContent, 0, 0, lp, true);
-        mCustomContentDescription = description;
-
-        mCustomContentCallbacks = callbacks;
-    }
-
     public void addExtraEmptyScreenOnDrag() {
         boolean lastChildOnScreen = false;
         boolean childOnFinalScreen = false;
@@ -800,7 +620,6 @@
         if (hasExtraEmptyScreen() || mScreenOrder.size() == 0) return;
         long finalScreenId = mScreenOrder.get(mScreenOrder.size() - 1);
 
-        if (finalScreenId == CUSTOM_CONTENT_SCREEN_ID) return;
         CellLayout finalScreen = mWorkspaceScreens.get(finalScreenId);
 
         // If the final screen is empty, convert it to the extra empty screen
@@ -809,7 +628,7 @@
             mWorkspaceScreens.remove(finalScreenId);
             mScreenOrder.remove(finalScreenId);
 
-            // if this is the last non-custom content screen, convert it to the empty screen
+            // if this is the last screen, convert it to the empty screen
             mWorkspaceScreens.put(EXTRA_EMPTY_SCREEN_ID, finalScreen);
             mScreenOrder.add(EXTRA_EMPTY_SCREEN_ID);
 
@@ -905,9 +724,7 @@
     }
 
     public boolean hasExtraEmptyScreen() {
-        int nScreens = getChildCount();
-        nScreens = nScreens - numCustomPages();
-        return mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID) && nScreens > 1;
+        return mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID) && getChildCount() > 1;
     }
 
     public long commitExtraEmptyScreen() {
@@ -988,7 +805,7 @@
 
         // We enforce at least one page to add new items to. In the case that we remove the last
         // such screen, we convert the last screen to the empty screen
-        int minScreens = 1 + numCustomPages();
+        int minScreens = 1;
 
         int pageShift = 0;
         for (Long id: removeScreens) {
@@ -1007,7 +824,7 @@
 
                 removeView(cl);
             } else {
-                // if this is the last non-custom content screen, convert it to the empty screen
+                // if this is the last screen, convert it to the empty screen
                 mRemoveEmptyScreenRunnable = null;
                 mWorkspaceScreens.put(EXTRA_EMPTY_SCREEN_ID, cl);
                 mScreenOrder.add(EXTRA_EMPTY_SCREEN_ID);
@@ -1156,10 +973,6 @@
                 || (mTransitionProgress > FINISHED_SWITCHING_STATE_TRANSITION_PROGRESS);
     }
 
-    protected void onWindowVisibilityChanged (int visibility) {
-        mLauncher.onWindowVisibilityChanged(visibility);
-    }
-
     @Override
     public boolean dispatchUnhandledMove(View focused, int direction) {
         if (workspaceInModalState() || !isFinishedSwitchingState()) {
@@ -1175,7 +988,6 @@
         case MotionEvent.ACTION_DOWN:
             mXDown = ev.getX();
             mYDown = ev.getY();
-            mTouchDownTime = System.currentTimeMillis();
             break;
         case MotionEvent.ACTION_POINTER_UP:
         case MotionEvent.ACTION_UP:
@@ -1190,41 +1002,6 @@
     }
 
     @Override
-    public boolean onGenericMotionEvent(MotionEvent event) {
-        // Ignore pointer scroll events if the custom content doesn't allow scrolling.
-        if ((getScreenIdForPageIndex(getCurrentPage()) == CUSTOM_CONTENT_SCREEN_ID)
-                && (mCustomContentCallbacks != null)
-                && !mCustomContentCallbacks.isScrollingAllowed()) {
-            return false;
-        }
-        return super.onGenericMotionEvent(event);
-    }
-
-    protected void reinflateWidgetsIfNecessary() {
-        final int clCount = getChildCount();
-        for (int i = 0; i < clCount; i++) {
-            CellLayout cl = (CellLayout) getChildAt(i);
-            ShortcutAndWidgetContainer swc = cl.getShortcutsAndWidgets();
-            final int itemCount = swc.getChildCount();
-            for (int j = 0; j < itemCount; j++) {
-                View v = swc.getChildAt(j);
-
-                if (v instanceof LauncherAppWidgetHostView
-                        && v.getTag() instanceof LauncherAppWidgetInfo) {
-                    LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) v.getTag();
-                    LauncherAppWidgetHostView lahv = (LauncherAppWidgetHostView) v;
-                    if (lahv.isReinflateRequired(mLauncher.getOrientation())) {
-                        // Remove and rebind the current widget (which was inflated in the wrong
-                        // orientation), but don't delete it from the database
-                        mLauncher.removeItem(lahv, info, false  /* deleteFromDb */);
-                        mLauncher.bindAppWidget(info);
-                    }
-                }
-            }
-        }
-    }
-
-    @Override
     protected void determineScrollingStart(MotionEvent ev) {
         if (!isFinishedSwitchingState()) return;
 
@@ -1241,24 +1018,6 @@
             cancelCurrentPageLongPress();
         }
 
-        boolean passRightSwipesToCustomContent =
-                (mTouchDownTime - mCustomContentShowTime) > CUSTOM_CONTENT_GESTURE_DELAY;
-
-        boolean swipeInIgnoreDirection = mIsRtl ? deltaX < 0 : deltaX > 0;
-        boolean onCustomContentScreen =
-                getScreenIdForPageIndex(getCurrentPage()) == CUSTOM_CONTENT_SCREEN_ID;
-        if (swipeInIgnoreDirection && onCustomContentScreen && passRightSwipesToCustomContent) {
-            // Pass swipes to the right to the custom content page.
-            return;
-        }
-
-        if (onCustomContentScreen && (mCustomContentCallbacks != null)
-                && !mCustomContentCallbacks.isScrollingAllowed()) {
-            // Don't allow workspace scrolling if the current custom content screen doesn't allow
-            // scrolling.
-            return;
-        }
-
         if (theta > MAX_SWIPE_ANGLE) {
             // Above MAX_SWIPE_ANGLE, we don't want to ever start scrolling the workspace
             return;
@@ -1279,12 +1038,12 @@
 
     protected void onPageBeginTransition() {
         super.onPageBeginTransition();
-        updateChildrenLayersEnabled(false);
+        updateChildrenLayersEnabled();
     }
 
     protected void onPageEndTransition() {
         super.onPageEndTransition();
-        updateChildrenLayersEnabled(false);
+        updateChildrenLayersEnabled();
 
         if (mDragController.isDragging()) {
             if (workspaceInModalState()) {
@@ -1294,15 +1053,6 @@
             }
         }
 
-        if (mDelayedResizeRunnable != null && !mIsSwitchingState) {
-            mDelayedResizeRunnable.run();
-            mDelayedResizeRunnable = null;
-        }
-
-        if (mDelayedSnapToPageRunnable != null) {
-            mDelayedSnapToPageRunnable.run();
-            mDelayedSnapToPageRunnable = null;
-        }
         if (mStripScreensOnPageStopMoving) {
             stripEmptyScreens();
             mStripScreensOnPageStopMoving = false;
@@ -1364,11 +1114,10 @@
         }
 
         updatePageAlphaValues();
-        updateStateForCustomContent();
         enableHwLayersOnVisiblePages();
     }
 
-    private void showPageIndicatorAtCurrentScroll() {
+    public void showPageIndicatorAtCurrentScroll() {
         if (mPageIndicator != null) {
             mPageIndicator.setScroll(getScrollX(), computeMaxScrollX());
         }
@@ -1376,9 +1125,6 @@
 
     @Override
     protected void overScroll(float amount) {
-        boolean shouldOverScroll = (amount <= 0 && (!hasCustomContent() || mIsRtl)) ||
-                (amount >= 0 && (!hasCustomContent() || !mIsRtl));
-
         boolean shouldScrollOverlay = mLauncherOverlay != null &&
                 ((amount <= 0 && !mIsRtl) || (amount >= 0 && mIsRtl));
 
@@ -1393,7 +1139,7 @@
 
             mLastOverlayScroll = Math.abs(amount / getViewportWidth());
             mLauncherOverlay.onScrollChange(mLastOverlayScroll, mIsRtl);
-        } else if (shouldOverScroll) {
+        } else {
             dampedOverScroll(amount);
         }
 
@@ -1409,8 +1155,6 @@
                 super.shouldFlingForVelocity(velocityX);
     }
 
-    private final Interpolator mAlphaInterpolator = new DecelerateInterpolator(3f);
-
     /**
      * The overlay scroll is being controlled locally, just update our overlay effect
      */
@@ -1435,7 +1179,7 @@
         scroll = Math.max(scroll - offset, 0);
         scroll = Math.min(1, scroll / (1 - offset));
 
-        float alpha = 1 - mAlphaInterpolator.getInterpolation(scroll);
+        float alpha = 1 - Interpolators.DEACCEL_3.getInterpolation(scroll);
         float transX = mLauncher.getDragLayer().getMeasuredWidth() * scroll;
         transX *= 1 - slip;
 
@@ -1527,9 +1271,7 @@
                 }
             });
 
-            AccessibilityManager am = (AccessibilityManager)
-                    mLauncher.getSystemService(Context.ACCESSIBILITY_SERVICE);
-            final boolean accessibilityEnabled = am.isEnabled();
+            final boolean accessibilityEnabled = isAccessibilityEnabled(mLauncher);
             animator.addUpdateListener(
                     new AlphaUpdateListener(mLauncher.getHotseat(), accessibilityEnabled));
             animator.addUpdateListener(
@@ -1546,22 +1288,6 @@
             mLauncher.getUserEventDispatcher().logActionOnContainer(Action.Touch.SWIPE,
                     swipeDirection, ContainerType.WORKSPACE, prevPage);
         }
-        if (hasCustomContent() && getNextPage() == 0 && !mCustomContentShowing) {
-            mCustomContentShowing = true;
-            if (mCustomContentCallbacks != null) {
-                mCustomContentCallbacks.onShow(false);
-                mCustomContentShowTime = System.currentTimeMillis();
-            }
-        } else if (hasCustomContent() && getNextPage() != 0 && mCustomContentShowing) {
-            mCustomContentShowing = false;
-            if (mCustomContentCallbacks != null) {
-                mCustomContentCallbacks.onHide();
-            }
-        }
-    }
-
-    protected CustomContentCallbacks getCustomContentCallbacks() {
-        return mCustomContentCallbacks;
     }
 
     protected void setWallpaperDimension() {
@@ -1588,26 +1314,6 @@
         }
     }
 
-    protected void snapToPage(int whichPage, Runnable r) {
-        snapToPage(whichPage, SLOW_PAGE_SNAP_ANIMATION_DURATION, r);
-    }
-
-    protected void snapToPage(int whichPage, int duration, Runnable r) {
-        if (mDelayedSnapToPageRunnable != null) {
-            mDelayedSnapToPageRunnable.run();
-        }
-        mDelayedSnapToPageRunnable = r;
-        snapToPage(whichPage, duration);
-    }
-
-    public void snapToScreenId(long screenId) {
-        snapToScreenId(screenId, null);
-    }
-
-    protected void snapToScreenId(long screenId, Runnable r) {
-        snapToPage(getPageIndexForScreenId(screenId), r);
-    }
-
     @Override
     public void computeScroll() {
         super.computeScroll();
@@ -1628,7 +1334,7 @@
     @Override
     public void announceForAccessibility(CharSequence text) {
         // Don't announce if apps is on top of us.
-        if (!mLauncher.isAppsViewVisible()) {
+        if (!mLauncher.isInState(ALL_APPS)) {
             super.announceForAccessibility(text);
         }
     }
@@ -1642,7 +1348,7 @@
     private void updatePageAlphaValues() {
         if (!workspaceInModalState() && !mIsSwitchingState) {
             int screenCenter = getScrollX() + getViewportWidth() / 2;
-            for (int i = numCustomPages(); i < getChildCount(); i++) {
+            for (int i = 0; i < getChildCount(); i++) {
                 CellLayout child = (CellLayout) getChildAt(i);
                 if (child != null) {
                     float scrollProgress = getScrollProgress(screenCenter, child, i);
@@ -1660,66 +1366,6 @@
         }
     }
 
-    public boolean hasCustomContent() {
-        return (mScreenOrder.size() > 0 && mScreenOrder.get(0) == CUSTOM_CONTENT_SCREEN_ID);
-    }
-
-    public int numCustomPages() {
-        return hasCustomContent() ? 1 : 0;
-    }
-
-    public boolean isOnOrMovingToCustomContent() {
-        return hasCustomContent() && getNextPage() == 0;
-    }
-
-    private void updateStateForCustomContent() {
-        float translationX = 0;
-        float progress = 0;
-        if (hasCustomContent()) {
-            int index = mScreenOrder.indexOf(CUSTOM_CONTENT_SCREEN_ID);
-
-            int scrollDelta = getScrollX() - getScrollForPage(index) -
-                    getLayoutTransitionOffsetForPage(index);
-            float scrollRange = getScrollForPage(index + 1) - getScrollForPage(index);
-            translationX = scrollRange - scrollDelta;
-            progress = (scrollRange - scrollDelta) / scrollRange;
-
-            if (mIsRtl) {
-                translationX = Math.min(0, translationX);
-            } else {
-                translationX = Math.max(0, translationX);
-            }
-            progress = Math.max(0, progress);
-        }
-
-        if (Float.compare(progress, mLastCustomContentScrollProgress) == 0) return;
-
-        CellLayout cc = mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID);
-        if (progress > 0 && cc.getVisibility() != VISIBLE && !workspaceInModalState()) {
-            cc.setVisibility(VISIBLE);
-        }
-
-        mLastCustomContentScrollProgress = progress;
-
-        // We should only update the drag layer background alpha if we are not in all apps or the
-        // widgets tray
-        if (mState == State.NORMAL) {
-            mLauncher.getDragLayer().setBackgroundAlpha(progress == 1 ? 0 : progress * 0.8f);
-        }
-
-        if (mLauncher.getHotseat() != null) {
-            mLauncher.getHotseat().setTranslationX(translationX);
-        }
-
-        if (mPageIndicator != null) {
-            mPageIndicator.setTranslationX(translationX);
-        }
-
-        if (mCustomContentCallbacks != null) {
-            mCustomContentCallbacks.onScrollProgressChanged(progress);
-        }
-    }
-
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
         IBinder windowToken = getWindowToken();
@@ -1733,10 +1379,6 @@
         mWallpaperOffset.setWindowToken(null);
     }
 
-    protected void onResume() {
-        mWallpaperOffset.onResume();
-    }
-
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         if (mUnlockWallpaperFromDefaultPageOnLayout) {
@@ -1759,18 +1401,18 @@
         return super.getDescendantFocusability();
     }
 
-    public boolean workspaceInModalState() {
-        return mState != State.NORMAL;
+    private boolean workspaceInModalState() {
+        return !mLauncher.isInState(NORMAL);
     }
 
     /** Returns whether a drag should be allowed to be started from the current workspace state. */
     public boolean workspaceIconsCanBeDragged() {
-        return mState == State.NORMAL || mState == State.SPRING_LOADED;
+        return mLauncher.isInState(NORMAL) || mLauncher.isInState(SPRING_LOADED);
     }
 
-    @Thunk void updateChildrenLayersEnabled(boolean force) {
-        boolean small = mState == State.OVERVIEW || mIsSwitchingState;
-        boolean enableChildrenLayers = force || small || mAnimatingViewIntoPlace || isPageInTransition();
+    private void updateChildrenLayersEnabled() {
+        boolean enableChildrenLayers =
+                isPageRearrangeEnabled() || mIsSwitchingState || isPageInTransition();
 
         if (enableChildrenLayers != mChildrenLayersEnabled) {
             mChildrenLayersEnabled = enableChildrenLayers;
@@ -1800,7 +1442,7 @@
 
             int leftScreen = -1;
             int rightScreen = -1;
-            for (int i = numCustomPages(); i < screenCount; i++) {
+            for (int i = 0; i < screenCount; i++) {
                 final View child = getPageAt(i);
 
                 float left = child.getLeft() + child.getTranslationX() - getScrollX();
@@ -1813,8 +1455,7 @@
             }
             if (mForceDrawAdjacentPages) {
                 // In overview mode, make sure that the two side pages are visible.
-                leftScreen = Utilities.boundToRange(getCurrentPage() - 1,
-                    numCustomPages(), rightScreen);
+                leftScreen = Utilities.boundToRange(getCurrentPage() - 1, 0, rightScreen);
                 rightScreen = Utilities.boundToRange(getCurrentPage() + 1,
                     leftScreen, getPageCount() - 1);
             }
@@ -1828,7 +1469,7 @@
                 }
             }
 
-            for (int i = numCustomPages(); i < screenCount; i++) {
+            for (int i = 0; i < screenCount; i++) {
                 final CellLayout layout = (CellLayout) getPageAt(i);
                 // enable layers between left and right screen inclusive.
                 boolean enableLayer = leftScreen <= i && i <= rightScreen;
@@ -1837,19 +1478,6 @@
         }
     }
 
-    public void buildPageHardwareLayers() {
-        // force layers to be enabled just for the call to buildLayer
-        updateChildrenLayersEnabled(true);
-        if (getWindowToken() != null) {
-            final int childCount = getChildCount();
-            for (int i = 0; i < childCount; i++) {
-                CellLayout cl = (CellLayout) getChildAt(i);
-                cl.buildHardwareLayer();
-            }
-        }
-        updateChildrenLayersEnabled(false);
-    }
-
     protected void onWallpaperTap(MotionEvent ev) {
         final int[] position = mTempXY;
         getLocationOnScreen(position);
@@ -1868,24 +1496,6 @@
         mOutlineProvider = outlineProvider;
     }
 
-    public void exitWidgetResizeMode() {
-        DragLayer dragLayer = mLauncher.getDragLayer();
-        dragLayer.clearResizeFrame();
-    }
-
-    @Override
-    protected void getFreeScrollPageRange(int[] range) {
-        getOverviewModePages(range);
-    }
-
-    private void getOverviewModePages(int[] range) {
-        int start = numCustomPages();
-        int end = getChildCount() - 1;
-
-        range[0] = Math.max(0, Math.min(start, getChildCount() - 1));
-        range[1] = Math.max(0, end);
-    }
-
     public void onStartReordering() {
         super.onStartReordering();
         // Reordering handles its own animations, disable the automatic ones.
@@ -1920,115 +1530,73 @@
         enableLayoutTransitions();
     }
 
-    public boolean isInOverviewMode() {
-        return mState == State.OVERVIEW;
-    }
-
     public void snapToPageFromOverView(int whichPage) {
-        mStateTransitionAnimation.snapToPageFromOverView(whichPage);
+        snapToPage(whichPage, OVERVIEW_TRANSITION_MS, Interpolators.ZOOM_IN);
     }
 
-    int getOverviewModeTranslationY() {
-        DeviceProfile grid = mLauncher.getDeviceProfile();
-        int overviewButtonBarHeight = grid.getOverviewModeButtonBarHeight();
+    private void onStartStateTransition(LauncherState state) {
+        mIsSwitchingState = true;
+        mTransitionProgress = 0;
 
-        int scaledHeight = (int) (mOverviewModeShrinkFactor * getNormalChildHeight());
-        Rect workspacePadding = grid.getWorkspacePadding(sTempRect);
-        int workspaceTop = mInsets.top + workspacePadding.top;
-        int workspaceBottom = getViewportHeight() - mInsets.bottom - workspacePadding.bottom;
-        int overviewTop = mInsets.top;
-        int overviewBottom = getViewportHeight() - mInsets.bottom - overviewButtonBarHeight;
-        int workspaceOffsetTopEdge = workspaceTop + ((workspaceBottom - workspaceTop) - scaledHeight) / 2;
-        int overviewOffsetTopEdge = overviewTop + (overviewBottom - overviewTop - scaledHeight) / 2;
-        return -workspaceOffsetTopEdge + overviewOffsetTopEdge;
+        updateChildrenLayersEnabled();
     }
 
-    float getSpringLoadedTranslationY() {
-        DeviceProfile grid = mLauncher.getDeviceProfile();
-        if (grid.isVerticalBarLayout() || getChildCount() == 0) {
-            return 0;
-        }
+    private void onEndStateTransition() {
+        mIsSwitchingState = false;
+        mForceDrawAdjacentPages = false;
+        mTransitionProgress = 1;
 
-        float scaledHeight = grid.workspaceSpringLoadShrinkFactor * getNormalChildHeight();
-        float shrunkTop = mInsets.top + grid.dropTargetBarSizePx;
-        float shrunkBottom = getViewportHeight() - mInsets.bottom
-                - grid.getWorkspacePadding(sTempRect).bottom
-                - grid.workspaceSpringLoadedBottomSpace;
-        float totalShrunkSpace = shrunkBottom - shrunkTop;
-
-        float desiredCellTop = shrunkTop + (totalShrunkSpace - scaledHeight) / 2;
-
-        float halfHeight = getHeight() / 2;
-        float myCenter = getTop() + halfHeight;
-        float cellTopFromCenter = halfHeight - getChildAt(0).getTop();
-        float actualCellTop = myCenter - cellTopFromCenter * grid.workspaceSpringLoadShrinkFactor;
-        return (desiredCellTop - actualCellTop) / grid.workspaceSpringLoadShrinkFactor;
-    }
-
-    float getOverviewModeShrinkFactor() {
-        return mOverviewModeShrinkFactor;
+        updateChildrenLayersEnabled();
+        updateAccessibilityFlags();
     }
 
     /**
-     * Sets the current workspace {@link State}, returning an animation transitioning the workspace
-     * to that new state.
+     * Sets the current workspace {@link LauncherState} and updates the UI without any animations
      */
-    public Animator setStateWithAnimation(State toState, boolean animated,
-            AnimationLayerSet layerViews) {
-        final State fromState = mState;
-
-        // Update the current state
-        mState = toState;
-
-        // Create the animation to the new state
-        AnimatorSet workspaceAnim =  mStateTransitionAnimation.getAnimationToState(fromState,
-                toState, animated, layerViews);
-
-        boolean shouldNotifyWidgetChange = !fromState.shouldUpdateWidget
-                && toState.shouldUpdateWidget;
-
-        updateAccessibilityFlags();
-
-        if (shouldNotifyWidgetChange) {
-            mLauncher.notifyWidgetProvidersChanged();
-        }
-
-        onPrepareStateTransition(mState.hasMultipleVisiblePages);
-
-        StateTransitionListener listener = new StateTransitionListener();
-        if (animated) {
-            ValueAnimator stepAnimator = ValueAnimator.ofFloat(0, 1);
-            stepAnimator.addUpdateListener(listener);
-
-            workspaceAnim.play(stepAnimator);
-            workspaceAnim.addListener(listener);
-        } else {
-            listener.onAnimationStart(null);
-            listener.onAnimationEnd(null);
-        }
-
-        return workspaceAnim;
+    @Override
+    public void setState(LauncherState toState) {
+        onStartStateTransition(toState);
+        mStateTransitionAnimation.setState(toState);
+        onEndStateTransition();
     }
 
-    public State getState() {
-        return mState;
+    /**
+     * Sets the current workspace {@link LauncherState}, then animates the UI
+     */
+    @Override
+    public void setStateWithAnimation(LauncherState toState, AnimationLayerSet layerViews,
+            AnimatorSet anim, AnimationConfig config) {
+        StateTransitionListener listener = new StateTransitionListener(toState);
+        mStateTransitionAnimation.setStateWithAnimation(toState, anim, layerViews, config);
+
+        // Invalidate the pages now, so that we have the visible pages before the
+        // animation is started
+        if (toState.hasMultipleVisiblePages) {
+            mForceDrawAdjacentPages = true;
+        }
+        invalidate(); // This will call dispatchDraw(), which calls getVisiblePages().
+
+        ValueAnimator stepAnimator = ValueAnimator.ofFloat(0, 1);
+        stepAnimator.addUpdateListener(listener);
+        stepAnimator.setDuration(config.duration);
+        anim.play(stepAnimator);
+        anim.addListener(listener);
     }
 
     public void updateAccessibilityFlags() {
         // TODO: Update the accessibility flags appropriately when dragging.
+        int accessibilityFlag = mLauncher.getStateManager().getState().workspaceAccessibilityFlag;
         if (!mLauncher.getAccessibilityDelegate().isInAccessibleDrag()) {
             int total = getPageCount();
-            for (int i = numCustomPages(); i < total; i++) {
-                updateAccessibilityFlags((CellLayout) getPageAt(i), i);
+            for (int i = 0; i < total; i++) {
+                updateAccessibilityFlags(accessibilityFlag, (CellLayout) getPageAt(i), i);
             }
-            setImportantForAccessibility((mState == State.NORMAL || mState == State.OVERVIEW)
-                    ? IMPORTANT_FOR_ACCESSIBILITY_AUTO
-                    : IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
+            setImportantForAccessibility(accessibilityFlag);
         }
     }
 
-    private void updateAccessibilityFlags(CellLayout page, int pageNo) {
-        if (mState == State.OVERVIEW) {
+    private void updateAccessibilityFlags(int accessibilityFlag, CellLayout page, int pageNo) {
+        if (isPageRearrangeEnabled()) {
             page.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
             page.getShortcutsAndWidgets().setImportantForAccessibility(
                     IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
@@ -2042,62 +1610,26 @@
                 page.setAccessibilityDelegate(mPagesAccessibilityDelegate);
             }
         } else {
-            int accessible = mState == State.NORMAL ?
-                    IMPORTANT_FOR_ACCESSIBILITY_AUTO :
-                        IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
             page.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
-            page.getShortcutsAndWidgets().setImportantForAccessibility(accessible);
+            page.getShortcutsAndWidgets().setImportantForAccessibility(accessibilityFlag);
             page.setContentDescription(null);
             page.setAccessibilityDelegate(null);
         }
     }
 
-    public void onPrepareStateTransition(boolean multiplePagesVisible) {
-        mIsSwitchingState = true;
-        mTransitionProgress = 0;
-
-        if (multiplePagesVisible) {
-            mForceDrawAdjacentPages = true;
-        }
-        invalidate(); // This will call dispatchDraw(), which calls getVisiblePages().
-
-        updateChildrenLayersEnabled(false);
-        hideCustomContentIfNecessary();
-    }
-
-    public void onEndStateTransition() {
-        mIsSwitchingState = false;
-        updateChildrenLayersEnabled(false);
-        showCustomContentIfNecessary();
-        mForceDrawAdjacentPages = false;
-        mTransitionProgress = 1;
-    }
-
-    void updateCustomContentVisibility() {
-        int visibility = mState == Workspace.State.NORMAL ? VISIBLE : INVISIBLE;
-        setCustomContentVisibility(visibility);
-    }
-
-    void setCustomContentVisibility(int visibility) {
-        if (hasCustomContent()) {
-            mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(visibility);
+    public void setPageRearrangeEnabled(boolean isEnabled) {
+        if (mPageRearrangeEnabled != isEnabled) {
+            mPageRearrangeEnabled = isEnabled;
+            if (isEnabled) {
+                enableFreeScroll();
+            } else {
+                disableFreeScroll();
+            }
         }
     }
 
-    void showCustomContentIfNecessary() {
-        boolean show  = mState == Workspace.State.NORMAL;
-        if (show && hasCustomContent()) {
-            mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(VISIBLE);
-        }
-    }
-
-    void hideCustomContentIfNecessary() {
-        boolean hide  = mState != Workspace.State.NORMAL;
-        if (hide && hasCustomContent()) {
-            disableLayoutTransitions();
-            mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(INVISIBLE);
-            enableLayoutTransitions();
-        }
+    public boolean isPageRearrangeEnabled() {
+        return mPageRearrangeEnabled;
     }
 
     public void startDrag(CellLayout.CellInfo cellInfo, DragOptions options) {
@@ -2144,7 +1676,7 @@
         mOutlineProvider = previewProvider;
 
         // The drag bitmap follows the touch point around on the screen
-        final Bitmap b = previewProvider.createDragBitmap(mCanvas);
+        final Bitmap b = previewProvider.createDragBitmap();
         int halfPadding = previewProvider.previewPadding / 2;
 
         float scale = previewProvider.getScaleAndPosition(b, mTempXY);
@@ -2191,19 +1723,19 @@
 
         DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source,
                 dragObject, dragVisualizeOffset, dragRect, scale, dragOptions);
-        dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor());
-        b.recycle();
+        dv.setIntrinsicIconScaleFactor(dragOptions.intrinsicIconScaleFactor);
         return dv;
     }
 
     private boolean transitionStateShouldAllowDrop() {
-        return ((!isSwitchingState() || mTransitionProgress > ALLOW_DROP_TRANSITION_PROGRESS) &&
-                (mState == State.NORMAL || mState == State.SPRING_LOADED));
+        return (!isSwitchingState() || mTransitionProgress > ALLOW_DROP_TRANSITION_PROGRESS) &&
+                workspaceIconsCanBeDragged();
     }
 
     /**
      * {@inheritDoc}
      */
+    @Override
     public boolean acceptDrop(DragObject d) {
         // If it's an external drop (e.g. from All Apps), check if it should be accepted
         CellLayout dropTargetLayout = mDropToLayout;
@@ -2335,8 +1867,7 @@
     }
 
     boolean createUserFolderIfNecessary(View newView, long container, CellLayout target,
-            int[] targetCell, float distance, boolean external, DragView dragView,
-            Runnable postAnimationRunnable) {
+            int[] targetCell, float distance, boolean external, DragView dragView) {
         if (distance > mMaxDistanceForFolderCreation) return false;
         View v = target.getChildAt(targetCell[0], targetCell[1]);
 
@@ -2380,8 +1911,8 @@
                 // folder background to the newly created icon. This preserves animation state.
                 fi.setFolderBackground(mFolderCreateBg);
                 mFolderCreateBg = new PreviewBackground();
-                fi.performCreateAnimation(destInfo, v, sourceInfo, dragView, folderLocation, scale,
-                        postAnimationRunnable);
+                fi.performCreateAnimation(destInfo, v, sourceInfo, dragView, folderLocation, scale
+                );
             } else {
                 fi.prepareCreateAnimation(v);
                 fi.addItem(destInfo);
@@ -2403,7 +1934,7 @@
         if (dropOverView instanceof FolderIcon) {
             FolderIcon fi = (FolderIcon) dropOverView;
             if (fi.acceptDrop(d.dragInfo)) {
-                fi.onDrop(d);
+                fi.onDrop(d, false /* itemReturnedOnFailedDrop */);
 
                 // if the drag started here, we need to remove it from the workspace
                 if (!external) {
@@ -2418,7 +1949,7 @@
     @Override
     public void prepareAccessibilityDrop() { }
 
-    public void onDrop(final DragObject d) {
+    public void onDrop(final DragObject d, DragOptions options) {
         mDragViewVisualCenter = d.getVisualCenter(mDragViewVisualCenter);
         CellLayout dropTargetLayout = mDropToLayout;
 
@@ -2435,13 +1966,14 @@
 
         int snapScreen = -1;
         boolean resizeOnDrop = false;
-        if (d.dragSource != this) {
+        if (d.dragSource != this || mDragInfo == null) {
             final int[] touchXY = new int[] { (int) mDragViewVisualCenter[0],
                     (int) mDragViewVisualCenter[1] };
             onDropExternal(touchXY, dropTargetLayout, d);
-        } else if (mDragInfo != null) {
+        } else {
             final View cell = mDragInfo.cell;
             boolean droppedOnOriginalCellDuringTransition = false;
+            Runnable onCompleteRunnable = null;
 
             if (dropTargetLayout != null && !d.cancelled) {
                 // Move internally
@@ -2465,12 +1997,10 @@
                 // If the item being dropped is a shortcut and the nearest drop
                 // cell also contains a shortcut, then create a folder with the two shortcuts.
                 if (createUserFolderIfNecessary(cell, container,
-                        dropTargetLayout, mTargetCell, distance, false, d.dragView, null)) {
-                    return;
-                }
-
-                if (addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell,
-                        distance, d, false)) {
+                        dropTargetLayout, mTargetCell, distance, false, d.dragView) ||
+                        addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell,
+                                distance, d, false)) {
+                    mLauncher.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
                     return;
                 }
 
@@ -2552,11 +2082,10 @@
                         AppWidgetProviderInfo pInfo = hostView.getAppWidgetInfo();
                         if (pInfo != null && pInfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE
                                 && !d.accessibleDrag) {
-                            mDelayedResizeRunnable = new Runnable() {
+                            onCompleteRunnable = new Runnable() {
                                 public void run() {
                                     if (!isPageInTransition()) {
-                                        DragLayer dragLayer = mLauncher.getDragLayer();
-                                        dragLayer.addResizeFrame(hostView, cellLayout);
+                                        AppWidgetResizeFrame.showForWidget(hostView, cellLayout);
                                     }
                                 }
                             };
@@ -2580,24 +2109,13 @@
             }
 
             final CellLayout parent = (CellLayout) cell.getParent().getParent();
-            // Prepare it to be animated into its new position
-            // This must be called after the view has been re-parented
-            final Runnable onCompleteRunnable = new Runnable() {
-                @Override
-                public void run() {
-                    mAnimatingViewIntoPlace = false;
-                    updateChildrenLayersEnabled(false);
-                }
-            };
-            mAnimatingViewIntoPlace = true;
             if (d.dragView.hasDrawn()) {
                 if (droppedOnOriginalCellDuringTransition) {
                     // Animate the item to its original position, while simultaneously exiting
                     // spring-loaded mode so the page meets the icon where it was picked up.
                     mLauncher.getDragController().animateDragViewToOriginalPosition(
-                            mDelayedResizeRunnable, cell,
-                            mStateTransitionAnimation.mSpringLoadedTransitionTime);
-                    mLauncher.exitSpringLoadedDragMode();
+                            onCompleteRunnable, cell, SPRING_LOADED_TRANSITION_MS);
+                    mLauncher.getStateManager().goToState(NORMAL);
                     mLauncher.getDropTargetBar().onDragEnd();
                     parent.onDropChild(cell);
                     return;
@@ -2608,19 +2126,22 @@
                 if (isWidget) {
                     int animationType = resizeOnDrop ? ANIMATE_INTO_POSITION_AND_RESIZE :
                             ANIMATE_INTO_POSITION_AND_DISAPPEAR;
-                    animateWidgetDrop(info, parent, d.dragView,
-                            onCompleteRunnable, animationType, cell, false);
+                    animateWidgetDrop(info, parent, d.dragView, null, animationType, cell, false);
                 } else {
                     int duration = snapScreen < 0 ? -1 : ADJACENT_SCREEN_DROP_DURATION;
                     mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, cell, duration,
-                            onCompleteRunnable, this);
+                            this);
                 }
             } else {
                 d.deferDragViewCleanupPostAnimation = false;
                 cell.setVisibility(VISIBLE);
             }
             parent.onDropChild(cell);
+
+            mLauncher.getStateManager().goToState(
+                    NORMAL, SPRING_LOADED_EXIT_DELAY, onCompleteRunnable);
         }
+
         if (d.stateAnnouncer != null && !droppedOnOriginalCell) {
             d.stateAnnouncer.completeAction(R.string.item_moved);
         }
@@ -2838,17 +2359,6 @@
        xy[1] = mTempXY[1];
    }
 
-   /*
-    *
-    * Convert the 2D coordinate xy from this CellLayout's coordinate space to
-    * the parent View's coordinate space. The argument xy is modified with the return result.
-    *
-    */
-   void mapPointFromChildToSelf(View v, float[] xy) {
-       xy[0] += v.getLeft();
-       xy[1] += v.getTop();
-   }
-
     private boolean isDragWidget(DragObject d) {
         return (d.dragInfo instanceof LauncherAppWidgetInfo ||
                 d.dragInfo instanceof PendingAddWidgetInfo);
@@ -2977,7 +2487,7 @@
         }
 
         // Always pick the current page.
-        if (layout == null && nextPage >= numCustomPages() && nextPage < getPageCount()) {
+        if (layout == null && nextPage >= 0 && nextPage < getPageCount()) {
             layout = (CellLayout) getChildAt(nextPage);
         }
         if (layout != mDragTargetLayout) {
@@ -2992,7 +2502,7 @@
      * Returns the child CellLayout if the point is inside the page coordinates, null otherwise.
      */
     private CellLayout verifyInsidePage(int pageNo, float[] touchXy)  {
-        if (pageNo >= numCustomPages() && pageNo < getPageCount()) {
+        if (pageNo >= 0 && pageNo < getPageCount()) {
             CellLayout cl = (CellLayout) getChildAt(pageNo);
             mapPointFromSelfToChild(cl, touchXy);
             if (touchXy[0] >= 0 && touchXy[0] <= cl.getWidth() &&
@@ -3139,14 +2649,6 @@
      * to add an item to one of the workspace screens.
      */
     private void onDropExternal(final int[] touchXY, final CellLayout cellLayout, DragObject d) {
-        final Runnable exitSpringLoadedRunnable = new Runnable() {
-            @Override
-            public void run() {
-                mLauncher.exitSpringLoadedDragModeDelayed(true,
-                        Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
-            }
-        };
-
         if (d.dragInfo instanceof PendingAddShortcutInfo) {
             ShortcutInfo si = ((PendingAddShortcutInfo) d.dragInfo)
                     .activityInfo.createShortcutInfo();
@@ -3169,8 +2671,8 @@
         final long screenId = getIdForScreen(cellLayout);
         if (!mLauncher.isHotseatLayout(cellLayout)
                 && screenId != getScreenIdForPageIndex(mCurrentPage)
-                && mState != State.SPRING_LOADED) {
-            snapToScreenId(screenId, null);
+                && !mLauncher.isInState(SPRING_LOADED)) {
+            snapToPage(getPageIndexForScreenId(screenId));
         }
 
         if (info instanceof PendingAddItemInfo) {
@@ -3244,6 +2746,8 @@
                     animationStyle, finalView, true);
         } else {
             // This is for other drag/drop cases, like dragging from All Apps
+            mLauncher.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
+
             View view;
 
             switch (info.itemType) {
@@ -3272,9 +2776,8 @@
                         cellLayout, mTargetCell);
                 float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0],
                         mDragViewVisualCenter[1], mTargetCell);
-                d.postAnimationRunnable = exitSpringLoadedRunnable;
                 if (createUserFolderIfNecessary(view, container, cellLayout, mTargetCell, distance,
-                        true, d.dragView, d.postAnimationRunnable)) {
+                        true, d.dragView)) {
                     return;
                 }
                 if (addToExistingFolderIfNecessary(view, cellLayout, mTargetCell, distance, d,
@@ -3305,16 +2808,15 @@
                 // We wrap the animation call in the temporary set and reset of the current
                 // cellLayout to its final transform -- this means we animate the drag view to
                 // the correct final location.
-                setFinalTransitionTransform(cellLayout);
-                mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, view,
-                        exitSpringLoadedRunnable, this);
-                resetTransitionTransform(cellLayout);
+                setFinalTransitionTransform();
+                mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, view, this);
+                resetTransitionTransform();
             }
         }
     }
 
     public Bitmap createWidgetBitmap(ItemInfo widgetInfo, View layout) {
-        int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(widgetInfo, false, true);
+        int[] unScaledSize = estimateItemSize(widgetInfo);
         int visibility = layout.getVisibility();
         layout.setVisibility(VISIBLE);
 
@@ -3322,12 +2824,9 @@
         int height = MeasureSpec.makeMeasureSpec(unScaledSize[1], MeasureSpec.EXACTLY);
         Bitmap b = Bitmap.createBitmap(unScaledSize[0], unScaledSize[1],
                 Bitmap.Config.ARGB_8888);
-        mCanvas.setBitmap(b);
-
         layout.measure(width, height);
         layout.layout(0, 0, unScaledSize[0], unScaledSize[1]);
-        layout.draw(mCanvas);
-        mCanvas.setBitmap(null);
+        layout.draw(new Canvas(b));
         layout.setVisibility(visibility);
         return b;
     }
@@ -3347,10 +2846,10 @@
         loc[0] = r.left;
         loc[1] = r.top;
 
-        setFinalTransitionTransform(layout);
+        setFinalTransitionTransform();
         float cellLayoutScale =
                 mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(layout, loc, true);
-        resetTransitionTransform(layout);
+        resetTransitionTransform();
 
         if (scale) {
             float dragViewScaleX = (1.0f * r.width()) / dragView.getMeasuredWidth();
@@ -3434,24 +2933,20 @@
         }
     }
 
-    public void setFinalTransitionTransform(CellLayout layout) {
+    public void setFinalTransitionTransform() {
         if (isSwitchingState()) {
             mCurrentScale = getScaleX();
             setScaleX(mStateTransitionAnimation.getFinalScale());
             setScaleY(mStateTransitionAnimation.getFinalScale());
         }
     }
-    public void resetTransitionTransform(CellLayout layout) {
+    public void resetTransitionTransform() {
         if (isSwitchingState()) {
             setScaleX(mCurrentScale);
             setScaleY(mCurrentScale);
         }
     }
 
-    public WorkspaceStateTransitionAnimation getStateTransitionAnimation() {
-        return mStateTransitionAnimation;
-    }
-
     /**
      * Return the current CellInfo describing our current drag; this method exists
      * so that Launcher can sync this object with the correct info when the activity is created/
@@ -3462,10 +2957,6 @@
         return mDragInfo;
     }
 
-    public int getCurrentPageOffsetFromCustomContent() {
-        return getNextPage() - numCustomPages();
-    }
-
     /**
      * Calculate the nearest cell where the given object would be dropped.
      *
@@ -3483,29 +2974,16 @@
 
         // hardware layers on children are enabled on startup, but should be disabled until
         // needed
-        updateChildrenLayersEnabled(false);
+        updateChildrenLayersEnabled();
     }
 
     /**
      * Called at the end of a drag which originated on the workspace.
      */
     public void onDropCompleted(final View target, final DragObject d,
-            final boolean isFlingToDelete, final boolean success) {
-        if (mDeferDropAfterUninstall) {
-            final CellLayout.CellInfo dragInfo = mDragInfo;
-            mDeferredAction = new Runnable() {
-                public void run() {
-                    mDragInfo = dragInfo; // Restore the drag info that was cleared in onDragEnd()
-                    onDropCompleted(target, d, isFlingToDelete, success);
-                    mDeferredAction = null;
-                }
-            };
-            return;
-        }
+            final boolean success) {
 
-        boolean beingCalledAfterUninstall = mDeferredAction != null;
-
-        if (success && !(beingCalledAfterUninstall && !mUninstallSuccessful)) {
+        if (success) {
             if (target != this && mDragInfo != null) {
                 removeWorkspaceItem(mDragInfo.cell);
             }
@@ -3519,18 +2997,10 @@
                         + "Workspace#onDropCompleted. Please file a bug. ");
             }
         }
-        if ((d.cancelled || (beingCalledAfterUninstall && !mUninstallSuccessful))
-                && mDragInfo != null && mDragInfo.cell != null) {
+        if (d.cancelled && mDragInfo != null && mDragInfo.cell != null) {
             mDragInfo.cell.setVisibility(VISIBLE);
         }
         mDragInfo = null;
-
-        if (!isFlingToDelete) {
-            // Fling to delete already exits spring loaded mode after the animation finishes.
-            mLauncher.exitSpringLoadedDragModeDelayed(success,
-                    Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, mDelayedResizeRunnable);
-            mDelayedResizeRunnable = null;
-        }
     }
 
     /**
@@ -3567,36 +3037,6 @@
         });
     }
 
-    @Override
-    public void deferCompleteDropAfterUninstallActivity() {
-        mDeferDropAfterUninstall = true;
-    }
-
-    /// maybe move this into a smaller part
-    @Override
-    public void onDragObjectRemoved(boolean success) {
-        mDeferDropAfterUninstall = false;
-        mUninstallSuccessful = success;
-        if (mDeferredAction != null) {
-            mDeferredAction.run();
-        }
-    }
-
-    @Override
-    public float getIntrinsicIconScaleFactor() {
-        return 1f;
-    }
-
-    @Override
-    public boolean supportsAppInfoDropTarget() {
-        return true;
-    }
-
-    @Override
-    public boolean supportsDeleteDropTarget() {
-        return true;
-    }
-
     public boolean isDropEnabled() {
         return true;
     }
@@ -3967,7 +3407,7 @@
                         .getInstance(mLauncher).findProvider(item.providerName, item.user);
             } else {
                 widgetInfo = AppWidgetManagerCompat.getInstance(mLauncher)
-                        .getAppWidgetInfo(item.appWidgetId);
+                        .getLauncherAppWidgetInfo(item.appWidgetId);
             }
 
             if (widgetInfo != null) {
@@ -3992,14 +3432,10 @@
         }
     }
 
-    void moveToDefaultScreen(boolean animate) {
-        int page = getDefaultPage();
+    void moveToDefaultScreen() {
+        int page = DEFAULT_PAGE;
         if (!workspaceInModalState() && getNextPage() != page) {
-            if (animate) {
-                snapToPage(page);
-            } else {
-                setCurrentPage(page);
-            }
+            snapToPage(page);
         }
         View child = getChildAt(page);
         if (child != null) {
@@ -4007,22 +3443,6 @@
         }
     }
 
-    void moveToCustomContentScreen(boolean animate) {
-        if (hasCustomContent()) {
-            int ccIndex = getPageIndexForScreenId(CUSTOM_CONTENT_SCREEN_ID);
-            if (animate) {
-                snapToPage(ccIndex);
-            } else {
-                setCurrentPage(ccIndex);
-            }
-            View child = getChildAt(ccIndex);
-            if (child != null) {
-                child.requestFocus();
-            }
-         }
-        exitWidgetResizeMode();
-    }
-
     @Override
     protected String getPageIndicatorDescription() {
         return getResources().getString(R.string.all_apps_button_label);
@@ -4030,16 +3450,12 @@
 
     @Override
     protected String getCurrentPageDescription() {
-        if (hasCustomContent() && getNextPage() == 0) {
-            return mCustomContentDescription;
-        }
         int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
         return getPageDescription(page);
     }
 
     private String getPageDescription(int page) {
-        int delta = numCustomPages();
-        int nScreens = getChildCount() - delta;
+        int nScreens = getChildCount();
         int extraScreenId = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID);
         if (extraScreenId >= 0 && nScreens > 1) {
             if (page == extraScreenId) {
@@ -4051,8 +3467,7 @@
             // When the workspace is not loaded, we do not know how many screen will be bound.
             return getContext().getString(R.string.all_apps_home_button_label);
         }
-        return getContext().getString(R.string.workspace_scroll_format,
-                page + 1 - delta, nScreens);
+        return getContext().getString(R.string.workspace_scroll_format, page + 1, nScreens);
     }
 
     @Override
@@ -4069,16 +3484,6 @@
         }
     }
 
-    @Override
-    public boolean enableFreeScroll() {
-        if (getState() == State.OVERVIEW) {
-            return super.enableFreeScroll();
-        } else {
-            Log.w(TAG, "enableFreeScroll called but not in overview: state=" + getState());
-            return false;
-        }
-    }
-
     /**
      * Used as a workaround to ensure that the AppWidgetService receives the
      * PACKAGE_ADDED broadcast before updating widgets.
@@ -4135,6 +3540,13 @@
 
     private class StateTransitionListener extends AnimatorListenerAdapter
             implements AnimatorUpdateListener {
+
+        private final LauncherState mToState;
+
+        StateTransitionListener(LauncherState toState) {
+            mToState = toState;
+        }
+
         @Override
         public void onAnimationUpdate(ValueAnimator anim) {
             mTransitionProgress = anim.getAnimatedFraction();
@@ -4142,11 +3554,7 @@
 
         @Override
         public void onAnimationStart(Animator animation) {
-            if (mState == State.SPRING_LOADED) {
-                // Show the page indicator at the same time as the rest of the transition.
-                showPageIndicatorAtCurrentScroll();
-            }
-            mTransitionProgress = 0;
+            onStartStateTransition(mToState);
         }
 
         @Override
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index a105a73..8edec40 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -16,6 +16,9 @@
 
 package com.android.launcher3;
 
+import static com.android.launcher3.LauncherAnimUtils.DRAWABLE_ALPHA;
+import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
@@ -24,17 +27,13 @@
 import android.animation.ValueAnimator;
 import android.content.Context;
 import android.content.res.Resources;
+import android.util.Property;
 import android.view.View;
-import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.animation.DecelerateInterpolator;
 
+import com.android.launcher3.LauncherStateManager.AnimationConfig;
 import com.android.launcher3.anim.AnimationLayerSet;
-import com.android.launcher3.anim.PropertyListBuilder;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.dragndrop.DragLayer;
-import com.android.launcher3.util.Thunk;
+import com.android.launcher3.anim.Interpolators;
 
 /**
  * A convenience class to update a view's visibility state after an alpha animation.
@@ -87,122 +86,20 @@
 }
 
 /**
- * This interpolator emulates the rate at which the perceived scale of an object changes
- * as its distance from a camera increases. When this interpolator is applied to a scale
- * animation on a view, it evokes the sense that the object is shrinking due to moving away
- * from the camera.
- */
-class ZInterpolator implements TimeInterpolator {
-    private float focalLength;
-
-    public ZInterpolator(float foc) {
-        focalLength = foc;
-    }
-
-    public float getInterpolation(float input) {
-        return (1.0f - focalLength / (focalLength + input)) /
-                (1.0f - focalLength / (focalLength + 1.0f));
-    }
-}
-
-/**
- * The exact reverse of ZInterpolator.
- */
-class InverseZInterpolator implements TimeInterpolator {
-    private ZInterpolator zInterpolator;
-    public InverseZInterpolator(float foc) {
-        zInterpolator = new ZInterpolator(foc);
-    }
-    public float getInterpolation(float input) {
-        return 1 - zInterpolator.getInterpolation(1 - input);
-    }
-}
-
-/**
- * InverseZInterpolator compounded with an ease-out.
- */
-class ZoomInInterpolator implements TimeInterpolator {
-    private final InverseZInterpolator inverseZInterpolator = new InverseZInterpolator(0.35f);
-    private final DecelerateInterpolator decelerate = new DecelerateInterpolator(3.0f);
-
-    public float getInterpolation(float input) {
-        return decelerate.getInterpolation(inverseZInterpolator.getInterpolation(input));
-    }
-}
-
-/**
- * Stores the transition states for convenience.
- */
-class TransitionStates {
-
-    // Raw states
-    final boolean oldStateIsNormal;
-    final boolean oldStateIsSpringLoaded;
-    final boolean oldStateIsNormalHidden;
-    final boolean oldStateIsOverviewHidden;
-    final boolean oldStateIsOverview;
-
-    final boolean stateIsNormal;
-    final boolean stateIsSpringLoaded;
-    final boolean stateIsNormalHidden;
-    final boolean stateIsOverviewHidden;
-    final boolean stateIsOverview;
-
-    // Convenience members
-    final boolean workspaceToAllApps;
-    final boolean overviewToAllApps;
-    final boolean allAppsToWorkspace;
-    final boolean workspaceToOverview;
-    final boolean overviewToWorkspace;
-
-    public TransitionStates(final Workspace.State fromState, final Workspace.State toState) {
-        oldStateIsNormal = (fromState == Workspace.State.NORMAL);
-        oldStateIsSpringLoaded = (fromState == Workspace.State.SPRING_LOADED);
-        oldStateIsNormalHidden = (fromState == Workspace.State.NORMAL_HIDDEN);
-        oldStateIsOverviewHidden = (fromState == Workspace.State.OVERVIEW_HIDDEN);
-        oldStateIsOverview = (fromState == Workspace.State.OVERVIEW);
-
-        stateIsNormal = (toState == Workspace.State.NORMAL);
-        stateIsSpringLoaded = (toState == Workspace.State.SPRING_LOADED);
-        stateIsNormalHidden = (toState == Workspace.State.NORMAL_HIDDEN);
-        stateIsOverviewHidden = (toState == Workspace.State.OVERVIEW_HIDDEN);
-        stateIsOverview = (toState == Workspace.State.OVERVIEW);
-
-        workspaceToOverview = (oldStateIsNormal && stateIsOverview);
-        workspaceToAllApps = (oldStateIsNormal && stateIsNormalHidden);
-        overviewToWorkspace = (oldStateIsOverview && stateIsNormal);
-        overviewToAllApps = (oldStateIsOverview && stateIsOverviewHidden);
-        allAppsToWorkspace = (oldStateIsNormalHidden && stateIsNormal);
-    }
-}
-
-/**
  * Manages the animations between each of the workspace states.
  */
 public class WorkspaceStateTransitionAnimation {
 
-    public static final String TAG = "WorkspaceStateTransitionAnimation";
+    public static final PropertySetter NO_ANIM_PROPERTY_SETTER = new PropertySetter();
 
-    @Thunk static final int BACKGROUND_FADE_OUT_DURATION = 350;
+    public final int mWorkspaceScrimAlpha;
 
-    final @Thunk Launcher mLauncher;
-    final @Thunk Workspace mWorkspace;
+    private final Launcher mLauncher;
+    private final Workspace mWorkspace;
 
-    @Thunk AnimatorSet mStateAnimator;
+    private final boolean mWorkspaceFadeInAdjacentScreens;
 
-    @Thunk float mCurrentScale;
-    @Thunk float mNewScale;
-
-    @Thunk final ZoomInInterpolator mZoomInInterpolator = new ZoomInInterpolator();
-
-    @Thunk float mSpringLoadedShrinkFactor;
-    @Thunk float mOverviewModeShrinkFactor;
-    @Thunk float mWorkspaceScrimAlpha;
-    @Thunk int mAllAppsTransitionTime;
-    @Thunk int mOverviewTransitionTime;
-    @Thunk int mOverlayTransitionTime;
-    @Thunk int mSpringLoadedTransitionTime;
-    @Thunk boolean mWorkspaceFadeInAdjacentScreens;
+    private float mNewScale;
 
     public WorkspaceStateTransitionAnimation(Launcher launcher, Workspace workspace) {
         mLauncher = launcher;
@@ -210,32 +107,19 @@
 
         DeviceProfile grid = mLauncher.getDeviceProfile();
         Resources res = launcher.getResources();
-        mAllAppsTransitionTime = res.getInteger(R.integer.config_allAppsTransitionTime);
-        mOverviewTransitionTime = res.getInteger(R.integer.config_overviewTransitionTime);
-        mOverlayTransitionTime = res.getInteger(R.integer.config_overlayTransitionTime);
-        mSpringLoadedTransitionTime = mOverlayTransitionTime / 2;
-        mSpringLoadedShrinkFactor = mLauncher.getDeviceProfile().workspaceSpringLoadShrinkFactor;
-        mOverviewModeShrinkFactor =
-                res.getInteger(R.integer.config_workspaceOverviewShrinkPercentage) / 100f;
-        mWorkspaceScrimAlpha = res.getInteger(R.integer.config_workspaceScrimAlpha) / 100f;
+        mWorkspaceScrimAlpha = res.getInteger(R.integer.config_workspaceScrimAlpha);
         mWorkspaceFadeInAdjacentScreens = grid.shouldFadeAdjacentWorkspaceScreens();
     }
 
-    public void snapToPageFromOverView(int whichPage) {
-        mWorkspace.snapToPage(whichPage, mOverviewTransitionTime, mZoomInInterpolator);
+    public void setState(LauncherState toState) {
+        setWorkspaceProperty(toState, NO_ANIM_PROPERTY_SETTER);
     }
 
-    public AnimatorSet getAnimationToState(Workspace.State fromState, Workspace.State toState,
-            boolean animated, AnimationLayerSet layerViews) {
-        AccessibilityManager am = (AccessibilityManager)
-                mLauncher.getSystemService(Context.ACCESSIBILITY_SERVICE);
-        final boolean accessibilityEnabled = am.isEnabled();
-        TransitionStates states = new TransitionStates(fromState, toState);
-        int workspaceDuration = getAnimationDuration(states);
-        animateWorkspace(states, animated, workspaceDuration, layerViews,
-                accessibilityEnabled);
-        animateBackgroundGradient(states, animated, BACKGROUND_FADE_OUT_DURATION);
-        return mStateAnimator;
+    public void setStateWithAnimation(LauncherState toState, AnimatorSet anim,
+            AnimationLayerSet layerViews, AnimationConfig config) {
+        AnimatedPropertySetter propertySetter =
+                new AnimatedPropertySetter(config.duration, layerViews, anim);
+        setWorkspaceProperty(toState, propertySetter);
     }
 
     public float getFinalScale() {
@@ -243,245 +127,127 @@
     }
 
     /**
-     * Returns the proper animation duration for a transition.
-     */
-    private int getAnimationDuration(TransitionStates states) {
-        if (states.workspaceToAllApps || states.overviewToAllApps) {
-            return mAllAppsTransitionTime;
-        } else if (states.workspaceToOverview || states.overviewToWorkspace) {
-            return mOverviewTransitionTime;
-        } else if (mLauncher.mState == Launcher.State.WORKSPACE_SPRING_LOADED
-                || states.oldStateIsNormal && states.stateIsSpringLoaded) {
-            return mSpringLoadedTransitionTime;
-        } else {
-            return mOverlayTransitionTime;
-        }
-    }
-
-    /**
      * Starts a transition animation for the workspace.
      */
-    private void animateWorkspace(final TransitionStates states, final boolean animated,
-            final int duration, AnimationLayerSet layerViews, final boolean accessibilityEnabled) {
-        // Cancel existing workspace animations and create a new animator set if requested
-        cancelAnimation();
-        if (animated) {
-            mStateAnimator = LauncherAnimUtils.createAnimatorSet();
-        }
-
-        // Update the workspace state
-        float finalBackgroundAlpha = (states.stateIsSpringLoaded || states.stateIsOverview) ?
-                1.0f : 0f;
-        float finalHotseatAlpha = (states.stateIsNormal || states.stateIsSpringLoaded ||
-                states.stateIsNormalHidden) ? 1f : 0f;
-        float finalQsbAlpha = (states.stateIsNormal || states.stateIsNormalHidden) ? 1f : 0f;
-
-        float finalWorkspaceTranslationY = 0;
-        if (states.stateIsOverview || states.stateIsOverviewHidden) {
-            finalWorkspaceTranslationY = mWorkspace.getOverviewModeTranslationY();
-        } else if (states.stateIsSpringLoaded) {
-            finalWorkspaceTranslationY = mWorkspace.getSpringLoadedTranslationY();
-        }
-
-        final int childCount = mWorkspace.getChildCount();
-        final int customPageCount = mWorkspace.numCustomPages();
-
-        mNewScale = 1.0f;
-
-        if (states.oldStateIsOverview) {
-            mWorkspace.disableFreeScroll();
-        } else if (states.stateIsOverview) {
-            mWorkspace.enableFreeScroll();
-        }
-
-        if (!states.stateIsNormal) {
-            if (states.stateIsSpringLoaded) {
-                mNewScale = mSpringLoadedShrinkFactor;
-            } else if (states.stateIsOverview || states.stateIsOverviewHidden) {
-                mNewScale = mOverviewModeShrinkFactor;
-            }
-        }
+    private void setWorkspaceProperty(LauncherState state, PropertySetter propertySetter) {
+        float[] scaleAndTranslationY = state.getWorkspaceScaleAndTranslation(mLauncher);
+        mNewScale = scaleAndTranslationY[0];
+        final float finalWorkspaceTranslationY = scaleAndTranslationY[1];
 
         int toPage = mWorkspace.getPageNearestToCenterOfScreen();
-        // TODO: Animate the celllayout alpha instead of the pages.
+        final int childCount = mWorkspace.getChildCount();
         for (int i = 0; i < childCount; i++) {
-            final CellLayout cl = (CellLayout) mWorkspace.getChildAt(i);
-            float initialAlpha = cl.getShortcutsAndWidgets().getAlpha();
-            float finalAlpha;
-            if (states.stateIsOverviewHidden) {
-                finalAlpha = 0f;
-            } else if(states.stateIsNormalHidden) {
-                finalAlpha = (i == mWorkspace.getNextPage()) ? 1 : 0;
-            } else if (states.stateIsNormal && mWorkspaceFadeInAdjacentScreens) {
-                finalAlpha = (i == toPage || i < customPageCount) ? 1f : 0f;
-            } else {
-                finalAlpha = 1f;
-            }
-
-            // If we are animating to/from the small state, then hide the side pages and fade the
-            // current page in
-            if (!FeatureFlags.NO_ALL_APPS_ICON && !mWorkspace.isSwitchingState()) {
-                if (states.workspaceToAllApps || states.allAppsToWorkspace) {
-                    boolean isCurrentPage = (i == toPage);
-                    if (states.allAppsToWorkspace && isCurrentPage) {
-                        initialAlpha = 0f;
-                    } else if (!isCurrentPage) {
-                        initialAlpha = finalAlpha = 0f;
-                    }
-                    cl.setShortcutAndWidgetAlpha(initialAlpha);
-                }
-            }
-
-            if (animated) {
-                float oldBackgroundAlpha = cl.getBackgroundAlpha();
-                if (initialAlpha != finalAlpha) {
-                    Animator alphaAnim = ObjectAnimator.ofFloat(
-                            cl.getShortcutsAndWidgets(), View.ALPHA, finalAlpha);
-                    alphaAnim.setDuration(duration)
-                            .setInterpolator(mZoomInInterpolator);
-                    mStateAnimator.play(alphaAnim);
-                }
-                if (oldBackgroundAlpha != 0 || finalBackgroundAlpha != 0) {
-                    ValueAnimator bgAnim = ObjectAnimator.ofFloat(cl, "backgroundAlpha",
-                            oldBackgroundAlpha, finalBackgroundAlpha);
-                    bgAnim.setInterpolator(mZoomInInterpolator);
-                    bgAnim.setDuration(duration);
-                    mStateAnimator.play(bgAnim);
-                }
-            } else {
-                cl.setBackgroundAlpha(finalBackgroundAlpha);
-                cl.setShortcutAndWidgetAlpha(finalAlpha);
-            }
+            applyChildState(state, (CellLayout) mWorkspace.getChildAt(i), i, toPage,
+                    propertySetter);
         }
 
-        final ViewGroup overviewPanel = mLauncher.getOverviewPanel();
+        propertySetter.setFloat(mWorkspace, SCALE_PROPERTY, mNewScale, Interpolators.ZOOM_IN);
+        propertySetter.setFloat(mWorkspace, View.TRANSLATION_Y,
+                finalWorkspaceTranslationY, Interpolators.ZOOM_IN);
 
-        float finalOverviewPanelAlpha = states.stateIsOverview ? 1f : 0f;
-        if (animated) {
-            // This is true when transitioning between:
-            // - Overview <-> Workspace
-            // - Overview <-> Widget Tray
-            if (finalOverviewPanelAlpha != overviewPanel.getAlpha()) {
-                Animator overviewPanelAlpha = ObjectAnimator.ofFloat(
-                        overviewPanel, View.ALPHA, finalOverviewPanelAlpha);
-                overviewPanelAlpha.addListener(new AlphaUpdateListener(overviewPanel,
-                        accessibilityEnabled));
-                layerViews.addView(overviewPanel);
+        // Set scrim
+        propertySetter.setInt(mLauncher.getDragLayer().getScrim(), DRAWABLE_ALPHA,
+                state.hasScrim ? mWorkspaceScrimAlpha : 0, Interpolators.DEACCEL_1_5);
+    }
 
-                if (states.overviewToWorkspace) {
-                    overviewPanelAlpha.setInterpolator(new DecelerateInterpolator(2));
-                } else if (states.workspaceToOverview) {
-                    overviewPanelAlpha.setInterpolator(null);
-                }
+    public void applyChildState(LauncherState state, CellLayout cl, int childIndex) {
+        applyChildState(state, cl, childIndex, mWorkspace.getPageNearestToCenterOfScreen(),
+                NO_ANIM_PROPERTY_SETTER);
+    }
 
-                overviewPanelAlpha.setDuration(duration);
-                mStateAnimator.play(overviewPanelAlpha);
-            }
+    private void applyChildState(LauncherState state, CellLayout cl, int childIndex,
+            int centerPage, PropertySetter propertySetter) {
+        propertySetter.setInt(cl.getScrimBackground(),
+                DRAWABLE_ALPHA, state.hasScrim ? 255 : 0, Interpolators.ZOOM_IN);
 
-            Animator scale = LauncherAnimUtils.ofPropertyValuesHolder(mWorkspace,
-                    new PropertyListBuilder().scale(mNewScale)
-                            .translationY(finalWorkspaceTranslationY).build())
-                    .setDuration(duration);
-            scale.setInterpolator(mZoomInInterpolator);
-            mStateAnimator.play(scale);
-
-            // For animation optimization, we may need to provide the Launcher transition
-            // with a set of views on which to force build and manage layers in certain scenarios.
-            layerViews.addView(mLauncher.getHotseat());
-            layerViews.addView(mWorkspace.getPageIndicator());
-
-            Animator hotseatAlpha = mWorkspace.createHotseatAlphaAnimator(finalHotseatAlpha);
-            if (states.workspaceToOverview) {
-                hotseatAlpha.setInterpolator(new DecelerateInterpolator(2));
-            } else if (states.overviewToWorkspace) {
-                hotseatAlpha.setInterpolator(null);
-            }
-            hotseatAlpha.setDuration(duration);
-            mStateAnimator.play(hotseatAlpha);
-            mStateAnimator.addListener(new AnimatorListenerAdapter() {
-                boolean canceled = false;
-                @Override
-                public void onAnimationCancel(Animator animation) {
-                    canceled = true;
-                }
-
-                @Override
-                public void onAnimationStart(Animator animation) {
-                    mWorkspace.getPageIndicator().setShouldAutoHide(!states.stateIsSpringLoaded);
-                }
-
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    mStateAnimator = null;
-                    if (canceled) return;
-                    if (accessibilityEnabled && overviewPanel.getVisibility() == View.VISIBLE) {
-                        overviewPanel.getChildAt(0).performAccessibilityAction(
-                                AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
-                    }
-                }
-            });
-        } else {
-            overviewPanel.setAlpha(finalOverviewPanelAlpha);
-            AlphaUpdateListener.updateVisibility(overviewPanel, accessibilityEnabled);
-            mWorkspace.getPageIndicator().setShouldAutoHide(!states.stateIsSpringLoaded);
-
-            mWorkspace.createHotseatAlphaAnimator(finalHotseatAlpha).end();
-            mWorkspace.updateCustomContentVisibility();
-            mWorkspace.setScaleX(mNewScale);
-            mWorkspace.setScaleY(mNewScale);
-            mWorkspace.setTranslationY(finalWorkspaceTranslationY);
-
-            if (accessibilityEnabled && overviewPanel.getVisibility() == View.VISIBLE) {
-                overviewPanel.getChildAt(0).performAccessibilityAction(
-                        AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
-            }
+        // Only animate the page alpha when we actually fade pages
+        if (mWorkspaceFadeInAdjacentScreens) {
+            float finalAlpha = state == LauncherState.NORMAL && childIndex != centerPage ? 0 : 1f;
+            propertySetter.setFloat(cl.getShortcutsAndWidgets(), View.ALPHA,
+                    finalAlpha, Interpolators.ZOOM_IN);
         }
     }
 
-    /**
-     * Animates the background scrim. Add to the state animator to prevent jankiness.
-     *
-     * @param states the current and final workspace states
-     * @param animated whether or not to set the background alpha immediately
-     * @duration duration of the animation
-     */
-    private void animateBackgroundGradient(TransitionStates states,
-            boolean animated, int duration) {
+    public static class PropertySetter {
 
-        final DragLayer dragLayer = mLauncher.getDragLayer();
-        final float startAlpha = dragLayer.getBackgroundAlpha();
-        float finalAlpha = states.stateIsNormal || states.stateIsNormalHidden ?
-                0 : mWorkspaceScrimAlpha;
-
-        if (finalAlpha != startAlpha) {
-            if (animated) {
-                // These properties refer to the background protection gradient used for AllApps
-                // and Widget tray.
-                ValueAnimator bgFadeOutAnimation = ValueAnimator.ofFloat(startAlpha, finalAlpha);
-                bgFadeOutAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-                    @Override
-                    public void onAnimationUpdate(ValueAnimator animation) {
-                        dragLayer.setBackgroundAlpha(
-                                ((Float)animation.getAnimatedValue()).floatValue());
-                    }
-                });
-                bgFadeOutAnimation.setInterpolator(new DecelerateInterpolator(1.5f));
-                bgFadeOutAnimation.setDuration(duration);
-                mStateAnimator.play(bgFadeOutAnimation);
-            } else {
-                dragLayer.setBackgroundAlpha(finalAlpha);
+        public void setViewAlpha(Animator anim, View view, float alpha) {
+            if (anim != null) {
+                anim.end();
+                return;
             }
+            view.setAlpha(alpha);
+            AlphaUpdateListener.updateVisibility(view, isAccessibilityEnabled(view));
+        }
+
+        public <T> void setFloat(T target, Property<T, Float> property, float value,
+                TimeInterpolator interpolator) {
+            property.set(target, value);
+        }
+
+        public <T> void setInt(T target, Property<T, Integer> property, int value,
+                TimeInterpolator interpolator) {
+            property.set(target, value);
+        }
+
+        protected boolean isAccessibilityEnabled(View v) {
+            AccessibilityManager am = (AccessibilityManager)
+                    v.getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
+            return am.isEnabled();
         }
     }
 
-    /**
-     * Cancels the current animation.
-     */
-    private void cancelAnimation() {
-        if (mStateAnimator != null) {
-            mStateAnimator.setDuration(0);
-            mStateAnimator.cancel();
+    public static class AnimatedPropertySetter extends PropertySetter {
+
+        private final long mDuration;
+        private final AnimationLayerSet mLayerViews;
+        private final AnimatorSet mStateAnimator;
+
+        public AnimatedPropertySetter(
+                long duration, AnimationLayerSet layerView, AnimatorSet anim) {
+            mDuration = duration;
+            mLayerViews = layerView;
+            mStateAnimator = anim;
         }
-        mStateAnimator = null;
+
+        @Override
+        public void setViewAlpha(Animator anim, View view, float alpha) {
+            if (anim == null) {
+                if (view.getAlpha() == alpha) {
+                    return;
+                }
+                anim = ObjectAnimator.ofFloat(view, View.ALPHA, alpha);
+                anim.addListener(new AlphaUpdateListener(view, isAccessibilityEnabled(view)));
+            }
+
+            anim.setDuration(mDuration).setInterpolator(getFadeInterpolator(alpha));
+            mLayerViews.addView(view);
+            mStateAnimator.play(anim);
+        }
+
+        @Override
+        public <T> void setFloat(T target, Property<T, Float> property, float value,
+                TimeInterpolator interpolator) {
+            if (property.get(target) == value) {
+                return;
+            }
+            Animator anim = ObjectAnimator.ofFloat(target, property, value);
+            anim.setDuration(mDuration).setInterpolator(interpolator);
+            mStateAnimator.play(anim);
+        }
+
+        @Override
+        public <T> void setInt(T target, Property<T, Integer> property, int value,
+                TimeInterpolator interpolator) {
+            if (property.get(target) == value) {
+                return;
+            }
+            Animator anim = ObjectAnimator.ofInt(target, property, value);
+            anim.setDuration(mDuration).setInterpolator(interpolator);
+            mStateAnimator.play(anim);
+        }
+
+        private TimeInterpolator getFadeInterpolator(float finalAlpha) {
+            return finalAlpha == 0 ? Interpolators.DEACCEL_2 : null;
+        }
     }
 }
\ No newline at end of file
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index a0ad07a..512db72 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -1,5 +1,7 @@
 package com.android.launcher3.accessibility;
 
+import static com.android.launcher3.LauncherState.NORMAL;
+
 import android.app.AlertDialog;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.DialogInterface;
@@ -17,6 +19,7 @@
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.AppWidgetResizeFrame;
 import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.ButtonDropTarget;
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeleteDropTarget;
 import com.android.launcher3.DropTarget.DragObject;
@@ -27,6 +30,7 @@
 import com.android.launcher3.LauncherAppWidgetHostView;
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.PendingAddItemInfo;
 import com.android.launcher3.R;
 import com.android.launcher3.ShortcutInfo;
@@ -45,9 +49,9 @@
 
     private static final String TAG = "LauncherAccessibilityDelegate";
 
-    protected static final int REMOVE = R.id.action_remove;
-    protected static final int INFO = R.id.action_info;
-    protected static final int UNINSTALL = R.id.action_uninstall;
+    public static final int REMOVE = R.id.action_remove;
+    public static final int INFO = R.id.action_info;
+    public static final int UNINSTALL = R.id.action_uninstall;
     protected static final int ADD_TO_WORKSPACE = R.id.action_add_to_workspace;
     protected static final int MOVE = R.id.action_move;
     protected static final int MOVE_TO_WORKSPACE = R.id.action_move_to_workspace;
@@ -108,14 +112,10 @@
             info.addAction(mActions.get(DEEP_SHORTCUTS));
         }
 
-        if (DeleteDropTarget.supportsAccessibleDrop(item)) {
-            info.addAction(mActions.get(REMOVE));
-        }
-        if (UninstallDropTarget.supportsDrop(host.getContext(), item)) {
-            info.addAction(mActions.get(UNINSTALL));
-        }
-        if (InfoDropTarget.supportsDrop(host.getContext(), item)) {
-            info.addAction(mActions.get(INFO));
+        for (ButtonDropTarget target : mLauncher.getDropTargetBar().getDropTargets()) {
+            if (target.supportsAccessibilityDrop(item)) {
+                info.addAction(mActions.get(target.getAccessibilityAction()));
+            }
         }
 
         // Do not add move actions for keyboard request as this uses virtual nodes.
@@ -148,27 +148,19 @@
     }
 
     public boolean performAction(final View host, final ItemInfo item, int action) {
-        if (action == REMOVE) {
-            DeleteDropTarget.removeWorkspaceOrFolderItem(mLauncher, item, host);
-            return true;
-        } else if (action == INFO) {
-            InfoDropTarget.startDetailsActivityForInfo(item, mLauncher, null);
-            return true;
-        } else if (action == UNINSTALL) {
-            return UninstallDropTarget.startUninstallActivity(mLauncher, item);
-        } else if (action == MOVE) {
+        if (action == MOVE) {
             beginAccessibleDrag(host, item);
         } else if (action == ADD_TO_WORKSPACE) {
             final int[] coordinates = new int[2];
             final long screenId = findSpaceOnWorkspace(item, coordinates);
-            mLauncher.showWorkspace(true, new Runnable() {
+            mLauncher.getStateManager().goToState(NORMAL, true, new Runnable() {
 
                 @Override
                 public void run() {
                     if (item instanceof AppInfo) {
                         ShortcutInfo info = ((AppInfo) item).makeShortcut();
                         mLauncher.getModelWriter().addItemToDatabase(info,
-                                LauncherSettings.Favorites.CONTAINER_DESKTOP,
+                                Favorites.CONTAINER_DESKTOP,
                                 screenId, coordinates[0], coordinates[1]);
 
                         ArrayList<ItemInfo> itemList = new ArrayList<>();
@@ -178,7 +170,7 @@
                         PendingAddItemInfo info = (PendingAddItemInfo) item;
                         Workspace workspace = mLauncher.getWorkspace();
                         workspace.snapToPage(workspace.getPageIndexForScreenId(screenId));
-                        mLauncher.addPendingItem(info, LauncherSettings.Favorites.CONTAINER_DESKTOP,
+                        mLauncher.addPendingItem(info, Favorites.CONTAINER_DESKTOP,
                                 screenId, coordinates, info.spanX, info.spanY);
                     }
                     announceConfirmation(R.string.item_added_to_workspace);
@@ -231,6 +223,13 @@
             return true;
         } else if (action == DEEP_SHORTCUTS) {
             return PopupContainerWithArrow.showForIcon((BubbleTextView) host) != null;
+        } else {
+            for (ButtonDropTarget dropTarget : mLauncher.getDropTargetBar().getDropTargets()) {
+                if (action == dropTarget.getAccessibilityAction()) {
+                    dropTarget.onAccessibilityDrop(host, item);
+                    return true;
+                }
+            }
         }
         return false;
     }
@@ -411,7 +410,7 @@
         CellLayout layout = (CellLayout) workspace.getPageAt(screenIndex);
 
         boolean found = layout.findCellForSpan(outCoordinates, info.spanX, info.spanY);
-        screenIndex = workspace.hasCustomContent() ? 1 : 0;
+        screenIndex = 0;
         while (!found && screenIndex < workspaceScreens.size()) {
             screenId = workspaceScreens.get(screenIndex);
             layout = (CellLayout) workspace.getPageAt(screenIndex);
diff --git a/src/com/android/launcher3/accessibility/OverviewScreenAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/OverviewScreenAccessibilityDelegate.java
index edb0b16..f9eb2ed 100644
--- a/src/com/android/launcher3/accessibility/OverviewScreenAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/OverviewScreenAccessibilityDelegate.java
@@ -88,7 +88,7 @@
             info.addAction(mActions.get(MOVE_FORWARD));
         }
 
-        int startIndex = mWorkspace.numCustomPages() + (FeatureFlags.QSB_ON_FIRST_SCREEN ? 1 : 0);
+        int startIndex = FeatureFlags.QSB_ON_FIRST_SCREEN ? 1 : 0;
         if (index > startIndex) {
             info.addAction(mActions.get(MOVE_BACKWARD));
         }
diff --git a/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java
index 5b7353a..cfb0520 100644
--- a/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.accessibility;
 
+import static com.android.launcher3.LauncherState.NORMAL;
+
 import android.view.View;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
@@ -79,9 +81,7 @@
                 }
             };
 
-            if (!mLauncher.showWorkspace(true, onComplete)) {
-                onComplete.run();
-            }
+            mLauncher.getStateManager().goToState(NORMAL, true, onComplete);
             return true;
         } else if (action == DISMISS_NOTIFICATION) {
             if (!(host instanceof NotificationMainView)) {
diff --git a/src/com/android/launcher3/allapps/AllAppsCaretController.java b/src/com/android/launcher3/allapps/AllAppsCaretController.java
deleted file mode 100644
index 583b49f..0000000
--- a/src/com/android/launcher3/allapps/AllAppsCaretController.java
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Copyright (C) 2016 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.allapps;
-
-import android.animation.ObjectAnimator;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
-
-import com.android.launcher3.Launcher;
-import com.android.launcher3.R;
-import com.android.launcher3.pageindicators.CaretDrawable;
-import com.android.launcher3.touch.SwipeDetector;
-
-public class AllAppsCaretController {
-    // Determines when the caret should flip. Should be accessed via getThreshold()
-    private static final float CARET_THRESHOLD = 0.015f;
-    private static final float CARET_THRESHOLD_LAND = 0.5f;
-    // The velocity at which the caret will peak (i.e. exhibit a 90 degree bend)
-    private static final float PEAK_VELOCITY = SwipeDetector.RELEASE_VELOCITY_PX_MS * .7f;
-
-    private Launcher mLauncher;
-
-    private ObjectAnimator mCaretAnimator;
-    private CaretDrawable mCaretDrawable;
-    private float mLastCaretProgress;
-    private boolean mThresholdCrossed;
-
-    public AllAppsCaretController(CaretDrawable caret, Launcher launcher) {
-        mLauncher = launcher;
-        mCaretDrawable = caret;
-
-        final long caretAnimationDuration = launcher.getResources().getInteger(
-                R.integer.config_caretAnimationDuration);
-        final Interpolator caretInterpolator = AnimationUtils.loadInterpolator(launcher,
-                android.R.interpolator.fast_out_slow_in);
-
-        // We will set values later
-        mCaretAnimator = ObjectAnimator.ofFloat(mCaretDrawable, "caretProgress", 0);
-        mCaretAnimator.setDuration(caretAnimationDuration);
-        mCaretAnimator.setInterpolator(caretInterpolator);
-    }
-
-    /**
-     * Updates the state of the caret based on the progress of the {@link AllAppsContainerView}, as
-     * defined by the {@link AllAppsTransitionController}. Uses the container's velocity to adjust
-     * angle of caret.
-     *
-     * @param containerProgress The progress of the container in the range [0..1]
-     * @param velocity The velocity of the container
-     * @param dragging {@code true} if the container is being dragged
-     */
-    public void updateCaret(float containerProgress, float velocity, boolean dragging) {
-        // If we're in portrait and the shift is not 0 or 1, adjust the caret based on velocity
-        if (getThreshold() < containerProgress && containerProgress < 1 - getThreshold() &&
-                !mLauncher.useVerticalBarLayout()) {
-            mThresholdCrossed = true;
-
-            // How fast are we moving as a percentage of the peak velocity?
-            final float pctOfFlingVelocity = Math.max(-1, Math.min(velocity / PEAK_VELOCITY, 1));
-
-            mCaretDrawable.setCaretProgress(pctOfFlingVelocity);
-
-            // Set the last caret progress to this progress to prevent animator cancellation
-            mLastCaretProgress = pctOfFlingVelocity;
-            // Animate to neutral. This is necessary so the caret doesn't "freeze" when the
-            // container stops moving (e.g., during a drag or when the threshold is reached).
-            animateCaretToProgress(CaretDrawable.PROGRESS_CARET_NEUTRAL);
-        } else if (!dragging) {
-            // Otherwise, if we're not dragging, match the caret to the appropriate state
-            if (containerProgress <= getThreshold()) { // All Apps is up
-                animateCaretToProgress(CaretDrawable.PROGRESS_CARET_POINTING_DOWN);
-            } else if (containerProgress >= 1 - getThreshold()) { // All Apps is down
-                animateCaretToProgress(CaretDrawable.PROGRESS_CARET_POINTING_UP);
-            }
-        }
-    }
-
-    private void animateCaretToProgress(float progress) {
-        // If the new progress is the same as the last progress we animated to, terminate early
-        if (Float.compare(mLastCaretProgress, progress) == 0) {
-            return;
-        }
-
-        if (mCaretAnimator.isRunning()) {
-            mCaretAnimator.cancel(); // Stop the animator in its tracks
-        }
-
-        // Update the progress and start the animation
-        mLastCaretProgress = progress;
-        mCaretAnimator.setFloatValues(progress);
-        mCaretAnimator.start();
-    }
-
-    private float getThreshold() {
-        // In landscape, just return the landscape threshold.
-        if (mLauncher.useVerticalBarLayout()) {
-            return CARET_THRESHOLD_LAND;
-        }
-
-        // Before the threshold is crossed, it is reported as zero. This makes the caret immediately
-        // responsive when a drag begins. After the threshold is crossed, we return the constant
-        // value. This means the caret will start its state-based adjustment sooner. That is, we
-        // won't have to wait until the panel is completely settled to begin animation.
-        return mThresholdCrossed ? CARET_THRESHOLD : 0f;
-    }
-
-    public void onDragStart() {
-        mThresholdCrossed = false;
-    }
-}
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 4eba5c6..efd7b97 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -16,67 +16,85 @@
 package com.android.launcher3.allapps;
 
 import android.content.Context;
-import android.graphics.Color;
+import android.graphics.Bitmap;
 import android.graphics.Rect;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.InsetDrawable;
+import android.os.Process;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.view.PagerAdapter;
+import android.support.v4.view.ViewPager;
 import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
 import android.text.Selection;
 import android.text.SpannableStringBuilder;
 import android.util.AttributeSet;
 import android.view.KeyEvent;
+import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.RelativeLayout;
 
 import com.android.launcher3.AppInfo;
-import com.android.launcher3.BaseContainerView;
 import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.DeleteDropTarget;
+import com.android.launcher3.ClickShadowView;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.DragSource;
 import com.android.launcher3.DropTarget;
+import com.android.launcher3.DropTarget.DragObject;
 import com.android.launcher3.Insettable;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
 import com.android.launcher3.PromiseAppInfo;
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.SpringAnimationHandler;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragOptions;
-import com.android.launcher3.folder.Folder;
 import com.android.launcher3.keyboard.FocusedItemDecorator;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.ComponentKeyMapper;
+import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.util.TransformingTouchDelegate;
+import com.android.launcher3.views.SlidingTabStrip;
 
+import java.util.HashMap;
 import java.util.List;
 import java.util.Set;
 
 /**
  * The all apps view container.
  */
-public class AllAppsContainerView extends BaseContainerView implements DragSource,
-        View.OnLongClickListener, Insettable {
+public class AllAppsContainerView extends RelativeLayout implements DragSource,
+        View.OnLongClickListener, Insettable, DeviceProfile.LauncherLayoutChangeListener,
+        BubbleTextView.BubbleTextShadowHandler {
+
+    protected final Rect mBasePadding = new Rect();
 
     private final Launcher mLauncher;
-    private final AlphabeticalAppsList mApps;
-    private final AllAppsGridAdapter mAdapter;
-    private final LinearLayoutManager mLayoutManager;
+    private final AdapterHolder[] mAH;
+    private final ClickShadowView mTouchFeedbackView;
+    private final ItemInfoMatcher mPersonalMatcher = ItemInfoMatcher.ofUser(Process.myUserHandle());
+    private final ItemInfoMatcher mWorkMatcher = ItemInfoMatcher.not(mPersonalMatcher);
 
-    private AllAppsRecyclerView mAppsRecyclerView;
     private SearchUiManager mSearchUiManager;
     private View mSearchContainer;
+    private InterceptingViewPager mViewPager;
+    private ViewGroup mHeader;
+    private FloatingHeaderHandler mFloatingHeaderHandler;
 
     private SpannableStringBuilder mSearchQueryBuilder = null;
 
     private int mNumAppsPerRow;
     private int mNumPredictedAppsPerRow;
 
-    private SpringAnimationHandler mSpringAnimationHandler;
+    private TransformingTouchDelegate mTouchDelegate;
+    private boolean mUsingTabs;
+
+    private final HashMap<ComponentKey, AppInfo> mComponentToAppMap = new HashMap<>();
 
     public AllAppsContainerView(Context context) {
         this(context, null);
@@ -90,71 +108,146 @@
         super(context, attrs, defStyleAttr);
 
         mLauncher = Launcher.getLauncher(context);
-        mApps = new AlphabeticalAppsList(context);
-        mAdapter = new AllAppsGridAdapter(mLauncher, mApps, mLauncher, this);
-        mSpringAnimationHandler = mAdapter.getSpringAnimationHandler();
-        mApps.setAdapter(mAdapter);
-        mLayoutManager = mAdapter.getLayoutManager();
+
         mSearchQueryBuilder = new SpannableStringBuilder();
 
         Selection.setSelection(mSearchQueryBuilder, 0);
+
+        mTouchFeedbackView = new ClickShadowView(context);
+        // Make the feedback view large enough to hold the blur bitmap.
+        int size = mLauncher.getDeviceProfile().allAppsIconSizePx
+                + mTouchFeedbackView.getExtraSize();
+        addView(mTouchFeedbackView, size, size);
+
+        mAH = new AdapterHolder[2];
+        mAH[AdapterHolder.MAIN] = new AdapterHolder();
+        mAH[AdapterHolder.WORK] = new AdapterHolder();
     }
 
     @Override
-    protected void updateBackground(
-            int paddingLeft, int paddingTop, int paddingRight, int paddingBottom) {
-        if (mLauncher.getDeviceProfile().isVerticalBarLayout()) {
-            getRevealView().setBackground(new InsetDrawable(mBaseDrawable,
-                    paddingLeft, paddingTop, paddingRight, paddingBottom));
-            getContentView().setBackground(
-                    new InsetDrawable(new ColorDrawable(Color.TRANSPARENT),
-                            paddingLeft, paddingTop, paddingRight, paddingBottom));
-        } else {
-            getRevealView().setBackground(mBaseDrawable);
-        }
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+
+        DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile();
+        grid.addLauncherLayoutChangedListener(this);
+
+        applyTouchDelegate();
+    }
+
+    private void applyTouchDelegate() {
+        RecyclerView rv = getActiveRecyclerView();
+        mTouchDelegate = new TransformingTouchDelegate(rv);
+        mTouchDelegate.setBounds(
+                rv.getLeft() - mBasePadding.left,
+                rv.getTop() - mBasePadding.top,
+                rv.getRight() + mBasePadding.right,
+                rv.getBottom() + mBasePadding.bottom);
+        setTouchDelegate(mTouchDelegate);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+
+        DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile();
+        grid.removeLauncherLayoutChangedListener(this);
     }
 
     /**
-     * Sets the current set of predicted apps.
+     * Calculate the background padding as it can change due to insets/content padding change.
      */
-    public void setPredictedApps(List<ComponentKeyMapper<AppInfo>> apps) {
-        mApps.setPredictedApps(apps);
+    @Override
+    public void onLauncherLayoutChanged() {
+        DeviceProfile grid = mLauncher.getDeviceProfile();
+        if (!grid.isVerticalBarLayout()) {
+            return;
+        }
+
+        int[] padding = grid.getContainerPadding();
+        int paddingLeft = padding[0];
+        int paddingRight = padding[1];
+        mBasePadding.set(paddingLeft, 0, paddingRight, 0);
+        setPadding(paddingLeft, 0, paddingRight, 0);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        applyTouchDelegate();
+    }
+
+    @Override
+    public void setPressedIcon(BubbleTextView icon, Bitmap background) {
+        mTouchFeedbackView.setPressedIcon(icon, background);
     }
 
     /**
      * Sets the current set of apps.
      */
     public void setApps(List<AppInfo> apps) {
-        mApps.setApps(apps);
+        boolean hasWorkProfileApp = hasWorkProfileApp(apps);
+        if (mUsingTabs != hasWorkProfileApp) {
+            rebindAdapters(hasWorkProfileApp);
+        }
+        mComponentToAppMap.clear();
+        addOrUpdateApps(apps);
     }
 
     /**
      * Adds or updates existing apps in the list
      */
     public void addOrUpdateApps(List<AppInfo> apps) {
-        mApps.addOrUpdateApps(apps);
-        mSearchUiManager.refreshSearchResult();
-    }
-
-    public void updatePromiseAppProgress(PromiseAppInfo app) {
-        int childCount = mAppsRecyclerView.getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            View child = mAppsRecyclerView.getChildAt(i);
-            if (child instanceof BubbleTextView && child.getTag() == app) {
-                BubbleTextView bubbleTextView = (BubbleTextView) child;
-                bubbleTextView.applyProgressLevel(app.level);
-            }
+        for (AppInfo app : apps) {
+            mComponentToAppMap.put(app.toComponentKey(), app);
         }
+        onAppsUpdated();
+        mSearchUiManager.refreshSearchResult();
     }
 
     /**
      * Removes some apps from the list.
      */
     public void removeApps(List<AppInfo> apps) {
-        mApps.removeApps(apps);
+        for (AppInfo app : apps) {
+            mComponentToAppMap.remove(app.toComponentKey());
+        }
+        onAppsUpdated();
         mSearchUiManager.refreshSearchResult();
     }
 
+    private void onAppsUpdated() {
+        for (int i = 0; i < getNumOfAdapters(); i++) {
+            mAH[i].appsList.onAppsUpdated();
+        }
+    }
+
+    private int getNumOfAdapters() {
+        return mUsingTabs ? mAH.length : 1;
+    }
+
+    public void updatePromiseAppProgress(PromiseAppInfo app) {
+        for (int i = 0; i < mAH.length; i++) {
+            updatePromiseAppProgress(app, mAH[i].recyclerView);
+        }
+        if (mFloatingHeaderHandler != null) {
+            updatePromiseAppProgress(app, mFloatingHeaderHandler.getContentView());
+        }
+    }
+
+    private void updatePromiseAppProgress(PromiseAppInfo app, ViewGroup parent) {
+        if (parent == null) {
+            return;
+        }
+        int childCount = parent.getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            View child = parent.getChildAt(i);
+            if (child instanceof BubbleTextView && child.getTag() == app) {
+                BubbleTextView bubbleTextView = (BubbleTextView) child;
+                bubbleTextView.applyProgressLevel(app.level);
+            }
+        }
+    }
+
     /**
      * Returns whether the view itself will handle the touch event or not.
      */
@@ -164,31 +257,31 @@
         if (mLauncher.getDragLayer().isEventOverView(mSearchContainer, ev)) {
             return true;
         }
-
-        int[] point = new int[2];
-        point[0] = (int) ev.getX();
-        point[1] = (int) ev.getY();
-        Utilities.mapCoordInSelfToDescendant(
-                mAppsRecyclerView.getScrollBar(), mLauncher.getDragLayer(), point);
-        // IF the MotionEvent is inside the thumb, container should not be pulled down.
-        if (mAppsRecyclerView.getScrollBar().shouldBlockIntercept(point[0], point[1])) {
-            return false;
-        }
-
-        // IF scroller is at the very top OR there is no scroll bar because there is probably not
-        // enough items to scroll, THEN it's okay for the container to be pulled down.
-        if (mAppsRecyclerView.getCurrentScrollY() == 0) {
+        if (mUsingTabs && mLauncher.getDragLayer().isEventOverView(mHeader, ev)) {
             return true;
         }
-        return false;
+        AllAppsRecyclerView rv = getActiveRecyclerView();
+        return rv == null || rv.shouldContainerScroll(ev, mLauncher.getDragLayer());
+    }
+
+    public AllAppsRecyclerView getActiveRecyclerView() {
+        if (!mUsingTabs || mViewPager.getCurrentItem() == 0) {
+            return mAH[AdapterHolder.MAIN].recyclerView;
+        } else {
+            return mAH[AdapterHolder.WORK].recyclerView;
+        }
     }
 
     /**
      * Resets the state of AllApps.
      */
     public void reset() {
+        for (int i = 0; i < mAH.length; i++) {
+            if (mAH[i].recyclerView != null) {
+                mAH[i].recyclerView.scrollToTop();
+            }
+        }
         // Reset the search bar and base recycler view after transitioning home
-        mAppsRecyclerView.scrollToTop();
         mSearchUiManager.reset();
     }
 
@@ -198,40 +291,23 @@
 
         // This is a focus listener that proxies focus from a view into the list view.  This is to
         // work around the search box from getting first focus and showing the cursor.
-        getContentView().setOnFocusChangeListener(new View.OnFocusChangeListener() {
+        setOnFocusChangeListener(new View.OnFocusChangeListener() {
             @Override
             public void onFocusChange(View v, boolean hasFocus) {
-                if (hasFocus) {
-                    mAppsRecyclerView.requestFocus();
+                if (hasFocus && getActiveRecyclerView() != null) {
+                    getActiveRecyclerView().requestFocus();
                 }
             }
         });
 
-        // Load the all apps recycler view
-        mAppsRecyclerView = findViewById(R.id.apps_list_view);
-        mAppsRecyclerView.setApps(mApps);
-        mAppsRecyclerView.setLayoutManager(mLayoutManager);
-        mAppsRecyclerView.setAdapter(mAdapter);
-        mAppsRecyclerView.setHasFixedSize(true);
-        // No animations will occur when changes occur to the items in this RecyclerView.
-        mAppsRecyclerView.setItemAnimator(null);
-        if (FeatureFlags.LAUNCHER3_PHYSICS) {
-            mAppsRecyclerView.setSpringAnimationHandler(mSpringAnimationHandler);
-        }
+        mHeader = findViewById(R.id.all_apps_header);
+        rebindAdapters(mUsingTabs);
 
         mSearchContainer = findViewById(R.id.search_container_all_apps);
         mSearchUiManager = (SearchUiManager) mSearchContainer;
-        mSearchUiManager.initialize(mApps, mAppsRecyclerView);
+        mSearchUiManager.initialize(this);
 
-
-        FocusedItemDecorator focusedItemDecorator = new FocusedItemDecorator(mAppsRecyclerView);
-        mAppsRecyclerView.addItemDecoration(focusedItemDecorator);
-        mAppsRecyclerView.preMeasureViews(mAdapter);
-        mAdapter.setIconFocusListener(focusedItemDecorator.getFocusListener());
-
-        getRevealView().setVisibility(View.VISIBLE);
-        getContentView().setVisibility(View.VISIBLE);
-        getContentView().setBackground(null);
+        onLauncherLayoutChanged();
     }
 
     public SearchUiManager getSearchUiManager() {
@@ -239,11 +315,6 @@
     }
 
     @Override
-    public View getTouchDelegateTargetView() {
-        return mAppsRecyclerView;
-    }
-
-    @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         DeviceProfile grid = mLauncher.getDeviceProfile();
         // Update the number of items in the grid before we measure the view
@@ -253,10 +324,9 @@
                 mNumPredictedAppsPerRow != grid.inv.numColumns) {
             mNumAppsPerRow = grid.inv.numColumns;
             mNumPredictedAppsPerRow = grid.inv.numColumns;
-
-            mAppsRecyclerView.setNumAppsPerRow(grid, mNumAppsPerRow);
-            mAdapter.setNumAppsPerRow(mNumAppsPerRow);
-            mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow);
+            for (int i = 0; i < mAH.length; i++) {
+                mAH[i].applyNumsPerRow();
+            }
         }
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
     }
@@ -270,7 +340,7 @@
     @Override
     public boolean onLongClick(final View v) {
         // When we have exited all apps or are in transition, disregard long clicks
-        if (!mLauncher.isAppsViewVisible() ||
+        if (!mLauncher.isInState(LauncherState.ALL_APPS) ||
                 mLauncher.getWorkspace().isSwitchingState()) return false;
         // Return if global dragging is not enabled or we are already dragging
         if (!mLauncher.isDraggingEnabled()) return false;
@@ -290,42 +360,16 @@
                 dragController.removeDragListener(this);
             }
         });
-        mLauncher.getWorkspace().beginDragShared(v, this, new DragOptions());
-        return false;
-    }
 
-    @Override
-    public boolean supportsAppInfoDropTarget() {
-        return true;
-    }
-
-    @Override
-    public boolean supportsDeleteDropTarget() {
-        return false;
-    }
-
-    @Override
-    public float getIntrinsicIconScaleFactor() {
         DeviceProfile grid = mLauncher.getDeviceProfile();
-        return (float) grid.allAppsIconSizePx / grid.iconSizePx;
+        DragOptions options = new DragOptions();
+        options.intrinsicIconScaleFactor = (float) grid.allAppsIconSizePx / grid.iconSizePx;
+        mLauncher.getWorkspace().beginDragShared(v, this, options);
+        return false;
     }
 
     @Override
-    public void onDropCompleted(View target, DropTarget.DragObject d, boolean isFlingToDelete,
-            boolean success) {
-        if (isFlingToDelete || !success || (target != mLauncher.getWorkspace() &&
-                !(target instanceof DeleteDropTarget) && !(target instanceof Folder))) {
-            // Exit spring loaded mode if we have not successfully dropped or have not handled the
-            // drop in Workspace
-            mLauncher.exitSpringLoadedDragModeDelayed(true,
-                    Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
-        }
-        mLauncher.unlockScreenOrientation(false);
-
-        if (!success) {
-            d.deferDragViewCleanupPostAnimation = false;
-        }
-    }
+    public void onDropCompleted(View target, DragObject d, boolean success) { }
 
     @Override
     public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
@@ -335,10 +379,10 @@
     @Override
     public void setInsets(Rect insets) {
         DeviceProfile grid = mLauncher.getDeviceProfile();
-        mAppsRecyclerView.setPadding(
-                mAppsRecyclerView.getPaddingLeft(), mAppsRecyclerView.getPaddingTop(),
-                mAppsRecyclerView.getPaddingRight(), insets.bottom);
-
+        for (int i = 0; i < mAH.length; i++) {
+            mAH[i].padding.bottom = insets.bottom;
+            mAH[i].applyPadding();
+        }
         if (grid.isVerticalBarLayout()) {
             ViewGroup.MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
             mlp.leftMargin = insets.left;
@@ -355,20 +399,303 @@
 
     public void updateIconBadges(Set<PackageUserKey> updatedBadges) {
         final PackageUserKey packageUserKey = new PackageUserKey(null, null);
-        final int n = mAppsRecyclerView.getChildCount();
-        for (int i = 0; i < n; i++) {
-            View child = mAppsRecyclerView.getChildAt(i);
-            if (!(child instanceof BubbleTextView) || !(child.getTag() instanceof ItemInfo)) {
-                continue;
-            }
-            ItemInfo info = (ItemInfo) child.getTag();
-            if (packageUserKey.updateFromItemInfo(info) && updatedBadges.contains(packageUserKey)) {
-                ((BubbleTextView) child).applyBadgeState(info, true /* animate */);
+        for (int j = 0; j < mAH.length; j++) {
+            if (mAH[j].recyclerView != null) {
+                final int n = mAH[j].recyclerView.getChildCount();
+                for (int i = 0; i < n; i++) {
+                    View child = mAH[j].recyclerView.getChildAt(i);
+                    if (!(child instanceof BubbleTextView) || !(child.getTag() instanceof ItemInfo)) {
+                        continue;
+                    }
+                    ItemInfo info = (ItemInfo) child.getTag();
+                    if (packageUserKey.updateFromItemInfo(info) && updatedBadges.contains(packageUserKey)) {
+                        ((BubbleTextView) child).applyBadgeState(info, true /* animate */);
+                    }
+                }
             }
         }
     }
 
     public SpringAnimationHandler getSpringAnimationHandler() {
-        return mSpringAnimationHandler;
+        return mUsingTabs ? null : mAH[AdapterHolder.MAIN].animationHandler;
     }
+
+    private void rebindAdapters(boolean showTabs) {
+        if (showTabs != mUsingTabs) {
+            replaceRVContainer(showTabs);
+        }
+        mUsingTabs = showTabs;
+
+        if (mUsingTabs) {
+            mAH[AdapterHolder.MAIN].setup(mViewPager.getChildAt(0), mPersonalMatcher);
+            mAH[AdapterHolder.WORK].setup(mViewPager.getChildAt(1), mWorkMatcher);
+            setupWorkProfileTabs();
+            setupHeader();
+            mHeader.setVisibility(View.VISIBLE);
+        } else {
+            mHeader.setVisibility(View.GONE);
+            mAH[AdapterHolder.MAIN].setup(findViewById(R.id.apps_list_view), null);
+        }
+
+        applyTouchDelegate();
+    }
+
+    private boolean hasWorkProfileApp(List<AppInfo> apps) {
+        if (FeatureFlags.ALL_APPS_TABS_ENABLED) {
+            for (AppInfo app : apps) {
+                if (mWorkMatcher.matches(app, null)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private void replaceRVContainer(boolean showTabs) {
+        for (int i = 0; i < mAH.length; i++) {
+            if (mAH[i].recyclerView != null) {
+                mAH[i].recyclerView.setLayoutManager(null);
+            }
+        }
+        View oldView = getRecyclerViewContainer();
+        int index = indexOfChild(oldView);
+        removeView(oldView);
+        int layout = showTabs ? R.layout.all_apps_tabs : R.layout.all_apps_rv_layout;
+        View newView = LayoutInflater.from(getContext()).inflate(layout, this, false);
+        addView(newView, index);
+        mViewPager = showTabs ? (InterceptingViewPager) newView : null;
+    }
+
+    public View getRecyclerViewContainer() {
+        return mViewPager != null ? mViewPager : findViewById(R.id.apps_list_view);
+    }
+
+    private void setupWorkProfileTabs() {
+        final SlidingTabStrip tabs = findViewById(R.id.tabs);
+        mViewPager.setAdapter(new TabsPagerAdapter());
+        mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
+
+            boolean mVisible = true;
+
+            @Override
+            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+                tabs.updateIndicatorPosition(position, positionOffset);
+                if (positionOffset == 0 && !mVisible || positionOffset > 0 && mVisible) {
+                    mVisible = positionOffset == 0;
+                    for (int i = 0; i < mAH.length; i++) {
+                        if (mAH[i].recyclerView != null) {
+                            mAH[i].recyclerView.getScrollbar().setAlpha(mVisible ? 1 : 0);
+                        }
+                    }
+                }
+            }
+
+            @Override
+            public void onPageSelected(int pos) {
+                tabs.updateTabTextColor(pos);
+                mFloatingHeaderHandler.setMainActive(pos == 0);
+                applyTouchDelegate();
+                if (mAH[pos].recyclerView != null) {
+                    mAH[pos].recyclerView.bindFastScrollbar();
+                }
+            }
+
+            @Override
+            public void onPageScrollStateChanged(int state) {
+            }
+        });
+
+        findViewById(R.id.tab_personal)
+                .setOnClickListener((View view) -> mViewPager.setCurrentItem(0));
+        findViewById(R.id.tab_work)
+                .setOnClickListener((View view) -> mViewPager.setCurrentItem(1));
+    }
+
+    public void setPredictedApps(List<ComponentKeyMapper<AppInfo>> apps) {
+        if (mUsingTabs) {
+            mFloatingHeaderHandler.getContentView().setPredictedApps(apps);
+        }
+        mAH[AdapterHolder.MAIN].appsList.setPredictedApps(apps);
+    }
+
+    public AppInfo findApp(ComponentKeyMapper<AppInfo> mapper) {
+        return mapper.getItem(mComponentToAppMap);
+    }
+
+    public AlphabeticalAppsList getApps() {
+        return mAH[AdapterHolder.MAIN].appsList;
+    }
+
+    public boolean isUsingTabs() {
+        return mUsingTabs;
+    }
+
+    public FloatingHeaderHandler getFloatingHeaderHandler() {
+        return mFloatingHeaderHandler;
+    }
+
+    private void setupHeader() {
+        int contentHeight = mLauncher.getDeviceProfile().allAppsCellHeightPx;
+        RecyclerView mainRV = mAH[AdapterHolder.MAIN].recyclerView;
+        RecyclerView workRV = mAH[AdapterHolder.WORK] != null
+                ? mAH[AdapterHolder.WORK].recyclerView : null;
+        mFloatingHeaderHandler = new FloatingHeaderHandler(mHeader, mainRV, workRV, contentHeight);
+        mFloatingHeaderHandler.getContentView().setNumAppsPerRow(mNumPredictedAppsPerRow);
+        mFloatingHeaderHandler.getContentView().setComponentToAppMap(mComponentToAppMap);
+        for (int i = 0; i < mAH.length; i++) {
+            mAH[i].paddingTopForTabs = contentHeight;
+            mAH[i].applyPadding();
+        }
+    }
+
+    public void setLastSearchQuery(String query) {
+        for (int i = 0; i < mAH.length; i++) {
+            mAH[i].adapter.setLastSearchQuery(query);
+        }
+    }
+
+    public void onSearchResultsChanged() {
+        for (int i = 0; i < mAH.length; i++) {
+            mAH[i].recyclerView.onSearchResultsChanged();
+        }
+    }
+
+    public void setRecyclerViewPaddingTop(int top) {
+        for (int i = 0; i < mAH.length; i++) {
+            mAH[i].padding.top = top;
+            mAH[i].applyPadding();
+        }
+    }
+
+    public void setRecyclerViewSidePadding(int left, int right) {
+        for (int i = 0; i < mAH.length; i++) {
+            mAH[i].padding.left = left;
+            mAH[i].padding.right = right;
+            mAH[i].applyPadding();
+        }
+    }
+
+    public void setRecyclerViewVerticalFadingEdgeEnabled(boolean enabled) {
+        for (int i = 0; i < mAH.length; i++) {
+            mAH[i].applyVerticalFadingEdgeEnabled(enabled);
+        }
+    }
+
+    public void addElevationController(RecyclerView.OnScrollListener scrollListener) {
+        if (!mUsingTabs) {
+            mAH[AdapterHolder.MAIN].recyclerView.addOnScrollListener(scrollListener);
+        }
+    }
+
+    public List<AppInfo> getPredictedApps() {
+        if (mUsingTabs) {
+            return mFloatingHeaderHandler.getContentView().getPredictedApps();
+        } else {
+            return mAH[AdapterHolder.MAIN].appsList.getPredictedApps();
+        }
+    }
+
+    public class AdapterHolder {
+        public static final int MAIN = 0;
+        public static final int WORK = 1;
+
+        final AllAppsGridAdapter adapter;
+        final LinearLayoutManager layoutManager;
+        final SpringAnimationHandler animationHandler;
+        final AlphabeticalAppsList appsList;
+        final Rect padding = new Rect();
+        int paddingTopForTabs;
+        AllAppsRecyclerView recyclerView;
+        boolean verticalFadingEdge;
+
+        AdapterHolder() {
+            appsList = new AlphabeticalAppsList(mLauncher, mComponentToAppMap);
+            adapter = new AllAppsGridAdapter(mLauncher, appsList, mLauncher,
+                    AllAppsContainerView.this, true);
+            appsList.setAdapter(adapter);
+            animationHandler = adapter.getSpringAnimationHandler();
+            layoutManager = adapter.getLayoutManager();
+        }
+
+        void setup(@NonNull View rv, @Nullable ItemInfoMatcher matcher) {
+            appsList.updateItemFilter(matcher);
+            recyclerView = (AllAppsRecyclerView) rv;
+            recyclerView.setApps(appsList, mUsingTabs);
+            recyclerView.setLayoutManager(layoutManager);
+            recyclerView.setAdapter(adapter);
+            recyclerView.setHasFixedSize(true);
+            // No animations will occur when changes occur to the items in this RecyclerView.
+            recyclerView.setItemAnimator(null);
+            if (FeatureFlags.LAUNCHER3_PHYSICS && animationHandler != null) {
+                recyclerView.setSpringAnimationHandler(animationHandler);
+            }
+            FocusedItemDecorator focusedItemDecorator = new FocusedItemDecorator(recyclerView);
+            recyclerView.addItemDecoration(focusedItemDecorator);
+            recyclerView.preMeasureViews(adapter);
+            adapter.setIconFocusListener(focusedItemDecorator.getFocusListener());
+            applyVerticalFadingEdgeEnabled(verticalFadingEdge);
+            applyPadding();
+            applyNumsPerRow();
+        }
+
+        void applyPadding() {
+            if (recyclerView != null) {
+                int paddingTop = mUsingTabs ? paddingTopForTabs : padding.top;
+                recyclerView.setPadding(padding.left, paddingTop, padding.right, padding.bottom);
+            }
+        }
+
+        void applyNumsPerRow() {
+            if (mNumAppsPerRow > 0) {
+                if (recyclerView != null) {
+                    recyclerView.setNumAppsPerRow(mLauncher.getDeviceProfile(), mNumAppsPerRow);
+                }
+                adapter.setNumAppsPerRow(mNumAppsPerRow);
+                appsList.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow);
+                if (mUsingTabs && mFloatingHeaderHandler != null) {
+                    mFloatingHeaderHandler.getContentView()
+                            .setNumAppsPerRow(mNumPredictedAppsPerRow);
+                }
+            }
+        }
+
+        public void applyVerticalFadingEdgeEnabled(boolean enabled) {
+            verticalFadingEdge = enabled;
+            mAH[AdapterHolder.MAIN].recyclerView.setVerticalFadingEdgeEnabled(!mUsingTabs
+                    && verticalFadingEdge);
+        }
+    }
+
+    private class TabsPagerAdapter extends PagerAdapter {
+        @Override
+        public int getCount() {
+            return 2;
+        }
+
+        @Override
+        public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
+            return view == object;
+        }
+
+        @NonNull
+        @Override
+        public Object instantiateItem(@NonNull ViewGroup container, int position) {
+            if (position == 0) {
+                return mAH[AdapterHolder.MAIN].recyclerView;
+            } else {
+                return mAH[AdapterHolder.WORK].recyclerView;
+            }
+        }
+
+        @Nullable
+        @Override
+        public CharSequence getPageTitle(int position) {
+            if (position == 0) {
+                return getResources().getString(R.string.all_apps_personal_tab);
+            } else {
+                return getResources().getString(R.string.all_apps_work_tab);
+            }
+        }
+    }
+
 }
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index 1f60fcc..ac8d367 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -206,10 +206,10 @@
     // The intent to send off to the market app, updated each time the search query changes.
     private Intent mMarketSearchIntent;
 
-    private SpringAnimationHandler<ViewHolder> mSpringAnimationHandler;
+    private final SpringAnimationHandler<ViewHolder> mSpringAnimationHandler;
 
     public AllAppsGridAdapter(Launcher launcher, AlphabeticalAppsList apps, View.OnClickListener
-            iconClickListener, View.OnLongClickListener iconLongClickListener) {
+            iconClickListener, View.OnLongClickListener iconLongClickListener, boolean springAnim) {
         Resources res = launcher.getResources();
         mLauncher = launcher;
         mApps = apps;
@@ -220,9 +220,11 @@
         mLayoutInflater = LayoutInflater.from(launcher);
         mIconClickListener = iconClickListener;
         mIconLongClickListener = iconLongClickListener;
-        if (FeatureFlags.LAUNCHER3_PHYSICS) {
+        if (FeatureFlags.LAUNCHER3_PHYSICS && springAnim) {
             mSpringAnimationHandler = new SpringAnimationHandler<>(
                     SpringAnimationHandler.Y_DIRECTION, new AllAppsSpringAnimationFactory());
+        } else {
+            mSpringAnimationHandler = null;
         }
     }
 
@@ -336,6 +338,7 @@
             case VIEW_TYPE_PREDICTION_ICON:
                 AppInfo info = mApps.getAdapterItems().get(position).appInfo;
                 BubbleTextView icon = (BubbleTextView) holder.itemView;
+                icon.reset();
                 icon.applyFromApplicationInfo(info);
                 break;
             case VIEW_TYPE_DISCOVERY_ITEM:
@@ -376,7 +379,7 @@
     @Override
     public void onViewAttachedToWindow(ViewHolder holder) {
         int type = holder.getItemViewType();
-        if (FeatureFlags.LAUNCHER3_PHYSICS && isViewType(type, VIEW_TYPE_MASK_HAS_SPRINGS)) {
+        if (mSpringAnimationHandler != null && isViewType(type, VIEW_TYPE_MASK_HAS_SPRINGS)) {
             mSpringAnimationHandler.add(holder.itemView, holder);
         }
     }
@@ -384,7 +387,7 @@
     @Override
     public void onViewDetachedFromWindow(ViewHolder holder) {
         int type = holder.getItemViewType();
-        if (FeatureFlags.LAUNCHER3_PHYSICS && isViewType(type, VIEW_TYPE_MASK_HAS_SPRINGS)) {
+        if (mSpringAnimationHandler != null && isViewType(type, VIEW_TYPE_MASK_HAS_SPRINGS)) {
             mSpringAnimationHandler.remove(holder.itemView);
         }
     }
@@ -424,6 +427,9 @@
         // The amount by which each adjacent rows' stiffness will differ.
         private static final float ROW_STIFFNESS_COEFFICIENT = 50f;
 
+        // The percentage by which we multiply each row to create the row factor.
+        private static final float ROW_PERCENTAGE = 0.3f;
+
         @Override
         public SpringAnimation initialize(ViewHolder vh) {
             return SpringAnimationHandler.forView(vh.itemView, DynamicAnimation.TRANSLATION_Y, 0);
@@ -462,7 +468,7 @@
          * effect.
          */
         private void calculateSpringValues(SpringAnimation spring, int row, int col) {
-            float rowFactor = (1 + row) * 0.5f;
+            float rowFactor = (1 + row) * ROW_PERCENTAGE;
             float colFactor = getColumnFactor(col, mAppsPerRow);
 
             float minValue = DEFAULT_MIN_VALUE_PX * (rowFactor + colFactor);
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index 494cd5a..09357f7 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -31,6 +31,7 @@
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.SpringAnimationHandler;
 import com.android.launcher3.config.FeatureFlags;
@@ -40,6 +41,7 @@
 import com.android.launcher3.touch.SwipeDetector;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
+import com.android.launcher3.views.RecyclerViewFastScroller;
 
 import java.util.List;
 
@@ -51,6 +53,7 @@
     private AlphabeticalAppsList mApps;
     private AllAppsFastScrollHelper mFastScrollHelper;
     private int mNumAppsPerRow;
+    private int mUserProfileTabContentHeight;
 
     // The specific view heights that we use to calculate scroll
     private SparseIntArray mViewHeights = new SparseIntArray();
@@ -63,7 +66,6 @@
     private SpringAnimationHandler mSpringAnimationHandler;
     private OverScrollHelper mOverScrollHelper;
     private SwipeDetector mPullDetector;
-
     private float mContentTranslationY = 0;
     public static final Property<AllAppsRecyclerView, Float> CONTENT_TRANS_Y =
             new Property<AllAppsRecyclerView, Float>(Float.class, "appsRecyclerViewContentTransY") {
@@ -122,9 +124,11 @@
     /**
      * Sets the list of apps in this view, used to determine the fastscroll position.
      */
-    public void setApps(AlphabeticalAppsList apps) {
+    public void setApps(AlphabeticalAppsList apps, boolean usingTabs) {
         mApps = apps;
         mFastScrollHelper = new AllAppsFastScrollHelper(this, apps);
+        mUserProfileTabContentHeight = usingTabs
+                ? Launcher.getLauncher(getContext()).getDeviceProfile().allAppsCellHeightPx : 0;;
     }
 
     public AlphabeticalAppsList getApps() {
@@ -136,7 +140,6 @@
      */
     public void setNumAppsPerRow(DeviceProfile grid, int numAppsPerRow) {
         mNumAppsPerRow = numAppsPerRow;
-
         RecyclerView.RecycledViewPool pool = getRecycledViewPool();
         int approxRows = (int) Math.ceil(grid.availableHeightPx / grid.allAppsIconSizePx);
         pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_EMPTY_SEARCH, 1);
@@ -169,7 +172,6 @@
                 AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET);
         putSameHeightFor(adapter, widthMeasureSpec, heightMeasureSpec,
                 AllAppsGridAdapter.VIEW_TYPE_EMPTY_SEARCH);
-
         if (FeatureFlags.DISCOVERY_ENABLED) {
             putSameHeightFor(adapter, widthMeasureSpec, heightMeasureSpec,
                     AllAppsGridAdapter.VIEW_TYPE_APPS_LOADING_DIVIDER);
@@ -485,8 +487,23 @@
      */
     @Override
     protected int getAvailableScrollHeight() {
-        return getPaddingTop() + getCurrentScrollY(mApps.getAdapterItems().size(), 0)
-                - getHeight() + getPaddingBottom();
+        return getPaddingTop() + getCurrentScrollY(getAdapter().getItemCount(), 0)
+                - getHeight() + getPaddingBottom() + mUserProfileTabContentHeight;
+    }
+
+    public int getScrollBarTop() {
+        return super.getScrollBarTop() + mUserProfileTabContentHeight;
+    }
+
+    /**
+     * Returns the height of the fast scroll bar
+     */
+    public int getScrollbarTrackHeight() {
+        return super.getScrollbarTrackHeight() + mUserProfileTabContentHeight;
+    }
+
+    public RecyclerViewFastScroller getScrollbar() {
+        return mScrollbar;
     }
 
     /**
@@ -587,7 +604,7 @@
         private void reset(boolean shouldSpring) {
             float y = getContentTranslationY();
             if (Float.compare(y, 0) != 0) {
-                if (FeatureFlags.LAUNCHER3_PHYSICS && shouldSpring) {
+                if (mSpringAnimationHandler != null && shouldSpring) {
                     // We calculate our own velocity to give the springs the desired effect.
                     float velocity = y / getDampedOverScroll(getHeight()) * MAX_RELEASE_VELOCITY;
                     // We want to negate the velocity because we are moving to 0 from -1 due to the
@@ -614,4 +631,5 @@
             return OverScroll.dampedScroll(y, getHeight());
         }
     }
+
 }
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java
deleted file mode 100644
index 517dc94..0000000
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2015 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.allapps;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.RelativeLayout;
-
-import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.BubbleTextView.BubbleTextShadowHandler;
-import com.android.launcher3.ClickShadowView;
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.R;
-
-/**
- * A container for RecyclerView to allow for the click shadow view to be shown behind an icon that
- * is launching.
- */
-public class AllAppsRecyclerViewContainerView extends RelativeLayout
-        implements BubbleTextShadowHandler {
-
-    private final ClickShadowView mTouchFeedbackView;
-
-    public AllAppsRecyclerViewContainerView(Context context) {
-        this(context, null);
-    }
-
-    public AllAppsRecyclerViewContainerView(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public AllAppsRecyclerViewContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-
-        Launcher launcher = Launcher.getLauncher(context);
-        DeviceProfile grid = launcher.getDeviceProfile();
-
-        mTouchFeedbackView = new ClickShadowView(context);
-
-        // Make the feedback view large enough to hold the blur bitmap.
-        int size = grid.allAppsIconSizePx + mTouchFeedbackView.getExtraSize();
-        addView(mTouchFeedbackView, size, size);
-    }
-
-    @Override
-    public void setPressedIcon(BubbleTextView icon, Bitmap background) {
-        if (icon == null || background == null) {
-            mTouchFeedbackView.setBitmap(null);
-            mTouchFeedbackView.animate().cancel();
-        } else if (mTouchFeedbackView.setBitmap(background)) {
-            View rv = findViewById(R.id.apps_list_view);
-            mTouchFeedbackView.alignWithIconView(icon, (ViewGroup) icon.getParent(), rv);
-            mTouchFeedbackView.animateShadow();
-        }
-    }
-}
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 743b16e..eb26704 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -1,37 +1,31 @@
 package com.android.launcher3.allapps;
 
+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.AnimatorInflater;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
-import android.animation.ArgbEvaluator;
 import android.animation.ObjectAnimator;
-import android.graphics.Color;
-import android.support.animation.SpringAnimation;
-import android.support.v4.graphics.ColorUtils;
-import android.support.v4.view.animation.FastOutSlowInInterpolator;
-import android.view.MotionEvent;
+import android.util.Property;
 import android.view.View;
-import android.view.animation.AccelerateInterpolator;
-import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
 
-import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.Hotseat;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAnimUtils;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager;
+import com.android.launcher3.LauncherStateManager.AnimationConfig;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
-import com.android.launcher3.anim.SpringAnimationHandler;
-import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.anim.AnimationLayerSet;
+import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.graphics.GradientView;
-import com.android.launcher3.touch.SwipeDetector;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.util.SystemUiController;
 import com.android.launcher3.util.Themes;
-import com.android.launcher3.util.TouchController;
 
 /**
  * Handles AllApps view transition.
@@ -43,35 +37,33 @@
  * If release velocity < THRES1, snap according to either top or bottom depending on whether it's
  * closer to top or closer to the page indicator.
  */
-public class AllAppsTransitionController implements TouchController, SwipeDetector.Listener,
-         SearchUiManager.OnScrollRangeChangeListener {
+public class AllAppsTransitionController
+        implements SearchUiManager.OnScrollRangeChangeListener, LauncherStateManager.StateHandler {
 
-    private static final String TAG = "AllAppsTrans";
-    private static final boolean DBG = false;
+    private static final Property<AllAppsTransitionController, Float> PROGRESS =
+            new Property<AllAppsTransitionController, Float>(Float.class, "progress") {
 
-    private final Interpolator mWorkspaceAccelnterpolator = new AccelerateInterpolator(2f);
-    private final Interpolator mHotseatAccelInterpolator = new AccelerateInterpolator(1.5f);
-    private final Interpolator mDecelInterpolator = new DecelerateInterpolator(3f);
-    private final Interpolator mFastOutSlowInInterpolator = new FastOutSlowInInterpolator();
-    private final SwipeDetector.ScrollInterpolator mScrollInterpolator
-            = new SwipeDetector.ScrollInterpolator();
+        @Override
+        public Float get(AllAppsTransitionController controller) {
+            return controller.mProgress;
+        }
+
+        @Override
+        public void set(AllAppsTransitionController controller, Float progress) {
+            controller.setProgress(progress);
+        }
+    };
+
+    private final Interpolator mWorkspaceAccelnterpolator = Interpolators.ACCEL_2;
+    private final Interpolator mHotseatAccelInterpolator = Interpolators.ACCEL_1_5;
 
     private static final float PARALLAX_COEFFICIENT = .125f;
-    private static final int SINGLE_FRAME_MS = 16;
 
     private AllAppsContainerView mAppsView;
-    private int mAllAppsBackgroundColor;
     private Workspace mWorkspace;
     private Hotseat mHotseat;
-    private int mHotseatBackgroundColor;
-
-    private AllAppsCaretController mCaretController;
-
-    private float mStatusBarHeight;
 
     private final Launcher mLauncher;
-    private final SwipeDetector mDetector;
-    private final ArgbEvaluator mEvaluator;
     private final boolean mIsDarkTheme;
 
     // Animation in this class is controlled by a single variable {@link mProgress}.
@@ -80,215 +72,36 @@
 
     // When {@link mProgress} is 0, all apps container is pulled up.
     // When {@link mProgress} is 1, all apps container is pulled down.
-    private float mShiftStart;      // [0, mShiftRange]
     private float mShiftRange;      // changes depending on the orientation
     private float mProgress;        // [0, 1], mShiftRange * mProgress = shiftCurrent
 
-    // Velocity of the container. Unit is in px/ms.
-    private float mContainerVelocity;
-
     private static final float DEFAULT_SHIFT_RANGE = 10;
 
-    private static final float RECATCH_REJECTION_FRACTION = .0875f;
-
-    private long mAnimationDuration;
-
-    private AnimatorSet mCurrentAnimation;
-    private boolean mNoIntercept;
-    private boolean mTouchEventStartedOnHotseat;
-
-    // Used in discovery bounce animation to provide the transition without workspace changing.
     private boolean mIsTranslateWithoutWorkspace = false;
     private Animator mDiscoBounceAnimation;
     private GradientView mGradientView;
 
-    private SpringAnimation mSearchSpring;
-    private SpringAnimationHandler mSpringAnimationHandler;
-
     public AllAppsTransitionController(Launcher l) {
         mLauncher = l;
-        mDetector = new SwipeDetector(l, this, SwipeDetector.VERTICAL);
         mShiftRange = DEFAULT_SHIFT_RANGE;
         mProgress = 1f;
 
-        mEvaluator = new ArgbEvaluator();
-        mAllAppsBackgroundColor = Themes.getAttrColor(l, android.R.attr.colorPrimary);
         mIsDarkTheme = Themes.getAttrBoolean(mLauncher, R.attr.isMainColorDark);
     }
 
-    @Override
-    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
-        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
-            mNoIntercept = false;
-            mTouchEventStartedOnHotseat = mLauncher.getDragLayer().isEventOverHotseat(ev);
-            if (!mLauncher.isAllAppsVisible() && mLauncher.getWorkspace().workspaceInModalState()) {
-                mNoIntercept = true;
-            } else if (mLauncher.isAllAppsVisible() &&
-                    !mAppsView.shouldContainerScroll(ev)) {
-                mNoIntercept = true;
-            } else if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
-                mNoIntercept = true;
-            } else {
-                // Now figure out which direction scroll events the controller will start
-                // calling the callbacks.
-                int directionsToDetectScroll = 0;
-                boolean ignoreSlopWhenSettling = false;
-
-                if (mDetector.isIdleState()) {
-                    if (mLauncher.isAllAppsVisible()) {
-                        directionsToDetectScroll |= SwipeDetector.DIRECTION_NEGATIVE;
-                    } else {
-                        directionsToDetectScroll |= SwipeDetector.DIRECTION_POSITIVE;
-                    }
-                } else {
-                    if (isInDisallowRecatchBottomZone()) {
-                        directionsToDetectScroll |= SwipeDetector.DIRECTION_POSITIVE;
-                    } else if (isInDisallowRecatchTopZone()) {
-                        directionsToDetectScroll |= SwipeDetector.DIRECTION_NEGATIVE;
-                    } else {
-                        directionsToDetectScroll |= SwipeDetector.DIRECTION_BOTH;
-                        ignoreSlopWhenSettling = true;
-                    }
-                }
-                mDetector.setDetectableScrollConditions(directionsToDetectScroll,
-                        ignoreSlopWhenSettling);
-            }
-        }
-
-        if (mNoIntercept) {
-            return false;
-        }
-        mDetector.onTouchEvent(ev);
-        if (mDetector.isSettlingState() && (isInDisallowRecatchBottomZone() || isInDisallowRecatchTopZone())) {
-            return false;
-        }
-        return mDetector.isDraggingOrSettling();
+    public float getShiftRange() {
+        return mShiftRange;
     }
 
-    @Override
-    public boolean onControllerTouchEvent(MotionEvent ev) {
-        if (hasSpringAnimationHandler()) {
-            mSpringAnimationHandler.addMovement(ev);
-        }
-        return mDetector.onTouchEvent(ev);
-    }
-
-    private boolean isInDisallowRecatchTopZone() {
-        return mProgress < RECATCH_REJECTION_FRACTION;
-    }
-
-    private boolean isInDisallowRecatchBottomZone() {
-        return mProgress > 1 - RECATCH_REJECTION_FRACTION;
-    }
-
-    @Override
-    public void onDragStart(boolean start) {
-        mCaretController.onDragStart();
-        cancelAnimation();
-        mCurrentAnimation = LauncherAnimUtils.createAnimatorSet();
-        mShiftStart = mAppsView.getTranslationY();
-        preparePull(start);
-        if (hasSpringAnimationHandler()) {
-            mSpringAnimationHandler.skipToEnd();
-        }
-    }
-
-    @Override
-    public boolean onDrag(float displacement, float velocity) {
-        if (mAppsView == null) {
-            return false;   // early termination.
-        }
-
-        mContainerVelocity = velocity;
-
-        float shift = Math.min(Math.max(0, mShiftStart + displacement), mShiftRange);
-        setProgress(shift / mShiftRange);
-
-        return true;
-    }
-
-    @Override
-    public void onDragEnd(float velocity, boolean fling) {
-        if (mAppsView == null) {
-            return; // early termination.
-        }
-
-        final int containerType = mTouchEventStartedOnHotseat
-                ? ContainerType.HOTSEAT : ContainerType.WORKSPACE;
-
-        if (fling) {
-            if (velocity < 0) {
-                calculateDuration(velocity, mAppsView.getTranslationY());
-
-                if (!mLauncher.isAllAppsVisible()) {
-                    mLauncher.getUserEventDispatcher().logActionOnContainer(
-                            Action.Touch.FLING,
-                            Action.Direction.UP,
-                            containerType);
-                }
-                mLauncher.showAppsView(true /* animated */, false /* updatePredictedApps */);
-                if (hasSpringAnimationHandler()) {
-                    mSpringAnimationHandler.add(mSearchSpring, true /* setDefaultValues */);
-                    // The icons are moving upwards, so we go to 0 from 1. (y-axis 1 is below 0.)
-                    mSpringAnimationHandler.animateToFinalPosition(0 /* pos */, 1 /* startValue */);
-                }
-            } else {
-                calculateDuration(velocity, Math.abs(mShiftRange - mAppsView.getTranslationY()));
-                mLauncher.showWorkspace(true);
-            }
-            // snap to top or bottom using the release velocity
-        } else {
-            if (mAppsView.getTranslationY() > mShiftRange / 2) {
-                calculateDuration(velocity, Math.abs(mShiftRange - mAppsView.getTranslationY()));
-                mLauncher.showWorkspace(true);
-            } else {
-                calculateDuration(velocity, Math.abs(mAppsView.getTranslationY()));
-                if (!mLauncher.isAllAppsVisible()) {
-                    mLauncher.getUserEventDispatcher().logActionOnContainer(
-                            Action.Touch.SWIPE,
-                            Action.Direction.UP,
-                            containerType);
-                }
-                mLauncher.showAppsView(true, /* animated */ false /* updatePredictedApps */);
-            }
-        }
-    }
-
-    public boolean isTransitioning() {
-        return mDetector.isDraggingOrSettling();
-    }
-
-    /**
-     * @param start {@code true} if start of new drag.
-     */
-    public void preparePull(boolean start) {
-        if (start) {
-            // Initialize values that should not change until #onDragEnd
-            mStatusBarHeight = mLauncher.getDragLayer().getInsets().top;
-            mHotseat.setVisibility(View.VISIBLE);
-            mHotseatBackgroundColor = mHotseat.getBackgroundDrawableColor();
-            mHotseat.setBackgroundTransparent(true /* transparent */);
-            if (!mLauncher.isAllAppsVisible()) {
-                mLauncher.tryAndUpdatePredictedApps();
-                mAppsView.setVisibility(View.VISIBLE);
-                if (!FeatureFlags.LAUNCHER3_GRADIENT_ALL_APPS) {
-                    mAppsView.setRevealDrawableColor(mHotseatBackgroundColor);
-                }
-            }
-        }
+    private void onProgressAnimationStart() {
+        // Initialize values that should not change until #onDragEnd
+        mHotseat.setVisibility(View.VISIBLE);
+        mAppsView.setVisibility(View.VISIBLE);
     }
 
     private void updateLightStatusBar(float shift) {
-        // Do not modify status bar on landscape as all apps is not full bleed.
-        if (!FeatureFlags.LAUNCHER3_GRADIENT_ALL_APPS
-                && mLauncher.getDeviceProfile().isVerticalBarLayout()) {
-            return;
-        }
-
         // Use a light system UI (dark icons) if all apps is behind at least half of the status bar.
-        boolean forceChange = FeatureFlags.LAUNCHER3_GRADIENT_ALL_APPS ?
-                shift <= mShiftRange / 4 :
-                shift <= mStatusBarHeight / 2;
+        boolean forceChange = shift <= mShiftRange / 4;
         if (forceChange) {
             mLauncher.getSystemUiController().updateUiState(
                     SystemUiController.UI_STATE_ALL_APPS, !mIsDarkTheme);
@@ -301,17 +114,21 @@
     private void updateAllAppsBg(float progress) {
         // gradient
         if (mGradientView == null) {
-            mGradientView = (GradientView) mLauncher.findViewById(R.id.gradient_bg);
-            mGradientView.setVisibility(View.VISIBLE);
+            mGradientView = mLauncher.findViewById(R.id.gradient_bg);
         }
         mGradientView.setProgress(progress);
     }
 
     /**
-     * @param progress       value between 0 and 1, 0 shows all apps and 1 shows workspace
+     * Note this method should not be called outside this class. This is public because it is used
+     * in xml-based animations which also handle updating the appropriate UI.
+     *
+     * @param progress value between 0 and 1, 0 shows all apps and 1 shows workspace
+     *
+     * @see #setState(LauncherState)
+     * @see #setStateWithAnimation(LauncherState, AnimationLayerSet, AnimatorSet, AnimationConfig)
      */
     public void setProgress(float progress) {
-        float shiftPrevious = mProgress * mShiftRange;
         mProgress = progress;
         float shiftCurrent = progress * mShiftRange;
 
@@ -320,18 +137,8 @@
         float workspaceAlpha = mWorkspaceAccelnterpolator.getInterpolation(workspaceHotseatAlpha);
         float hotseatAlpha = mHotseatAccelInterpolator.getInterpolation(workspaceHotseatAlpha);
 
-        int color = (Integer) mEvaluator.evaluate(mDecelInterpolator.getInterpolation(alpha),
-                mHotseatBackgroundColor, mAllAppsBackgroundColor);
-        int bgAlpha = Color.alpha((int) mEvaluator.evaluate(alpha,
-                mHotseatBackgroundColor, mAllAppsBackgroundColor));
-
-        if (FeatureFlags.LAUNCHER3_GRADIENT_ALL_APPS) {
-            updateAllAppsBg(alpha);
-        } else {
-            mAppsView.setRevealDrawableColor(ColorUtils.setAlphaComponent(color, bgAlpha));
-        }
-
-        mAppsView.getContentView().setAlpha(alpha);
+        updateAllAppsBg(alpha);
+        mAppsView.setAlpha(alpha);
         mAppsView.setTranslationY(shiftCurrent);
 
         if (!mLauncher.getDeviceProfile().isVerticalBarLayout()) {
@@ -349,12 +156,6 @@
         mWorkspace.setWorkspaceYTranslationAndAlpha(
                 PARALLAX_COEFFICIENT * (-mShiftRange + shiftCurrent), workspaceAlpha);
 
-        if (!mDetector.isDraggingState()) {
-            mContainerVelocity = mDetector.computeVelocity(shiftCurrent - shiftPrevious,
-                    System.currentTimeMillis());
-        }
-
-        mCaretController.updateCaret(progress, mContainerVelocity, mDetector.isDraggingState());
         updateLightStatusBar(shiftCurrent);
     }
 
@@ -362,58 +163,47 @@
         return mProgress;
     }
 
-    private void calculateDuration(float velocity, float disp) {
-        mAnimationDuration = SwipeDetector.calculateDuration(velocity, disp / mShiftRange);
+    /**
+     * Sets the vertical transition progress to {@param state} and updates all the dependent UI
+     * accordingly.
+     */
+    @Override
+    public void setState(LauncherState state) {
+        setProgress(state.verticalProgress);
+        onProgressAnimationEnd();
     }
 
-    public boolean animateToAllApps(AnimatorSet animationOut, long duration) {
-        boolean shouldPost = true;
-        if (animationOut == null) {
-            return shouldPost;
-        }
-        Interpolator interpolator;
-        if (mDetector.isIdleState()) {
-            preparePull(true);
-            mAnimationDuration = duration;
-            mShiftStart = mAppsView.getTranslationY();
-            interpolator = mFastOutSlowInInterpolator;
-        } else {
-            mScrollInterpolator.setVelocityAtZero(Math.abs(mContainerVelocity));
-            interpolator = mScrollInterpolator;
-            float nextFrameProgress = mProgress + mContainerVelocity * SINGLE_FRAME_MS / mShiftRange;
-            if (nextFrameProgress >= 0f) {
-                mProgress = nextFrameProgress;
-            }
-            shouldPost = false;
+    /**
+     * Creates an animation which updates the vertical transition progress and updates all the
+     * dependent UI using various animation events
+     */
+    @Override
+    public void setStateWithAnimation(LauncherState toState, AnimationLayerSet layerViews,
+            AnimatorSet animationOut, AnimationConfig config) {
+        if (Float.compare(mProgress, toState.verticalProgress) == 0) {
+            // Fail fast
+            onProgressAnimationEnd();
+            return;
         }
 
-        ObjectAnimator driftAndAlpha = ObjectAnimator.ofFloat(this, "progress",
-                mProgress, 0f);
-        driftAndAlpha.setDuration(mAnimationDuration);
-        driftAndAlpha.setInterpolator(interpolator);
-        animationOut.play(driftAndAlpha);
-
-        animationOut.addListener(new AnimatorListenerAdapter() {
-            boolean canceled = false;
-
+        Interpolator interpolator = config.userControlled ? LINEAR : FAST_OUT_SLOW_IN;
+        ObjectAnimator anim = ObjectAnimator.ofFloat(
+                this, PROGRESS, mProgress, toState.verticalProgress);
+        anim.setDuration(config.duration);
+        anim.setInterpolator(interpolator);
+        anim.addListener(new AnimationSuccessListener() {
             @Override
-            public void onAnimationCancel(Animator animation) {
-                canceled = true;
+            public void onAnimationSuccess(Animator animator) {
+                onProgressAnimationEnd();
             }
 
             @Override
-            public void onAnimationEnd(Animator animation) {
-                if (canceled) {
-                    return;
-                } else {
-                    finishPullUp();
-                    cleanUpAnimation();
-                    mDetector.finishedScrolling();
-                }
+            public void onAnimationStart(Animator animation) {
+                onProgressAnimationStart();
             }
         });
-        mCurrentAnimation = animationOut;
-        return shouldPost;
+
+        animationOut.play(anim);
     }
 
     public void showDiscoveryBounce() {
@@ -427,12 +217,12 @@
             @Override
             public void onAnimationStart(Animator animator) {
                 mIsTranslateWithoutWorkspace = true;
-                preparePull(true);
+                onProgressAnimationStart();
             }
 
             @Override
             public void onAnimationEnd(Animator animator) {
-                finishPullDown();
+                onProgressAnimationEnd();
                 mDiscoBounceAnimation = null;
                 mIsTranslateWithoutWorkspace = false;
             }
@@ -449,84 +239,6 @@
         });
     }
 
-    public boolean animateToWorkspace(AnimatorSet animationOut, long duration) {
-        boolean shouldPost = true;
-        if (animationOut == null) {
-            return shouldPost;
-        }
-        Interpolator interpolator;
-        if (mDetector.isIdleState()) {
-            preparePull(true);
-            mAnimationDuration = duration;
-            mShiftStart = mAppsView.getTranslationY();
-            interpolator = mFastOutSlowInInterpolator;
-        } else {
-            mScrollInterpolator.setVelocityAtZero(Math.abs(mContainerVelocity));
-            interpolator = mScrollInterpolator;
-            float nextFrameProgress = mProgress + mContainerVelocity * SINGLE_FRAME_MS / mShiftRange;
-            if (nextFrameProgress <= 1f) {
-                mProgress = nextFrameProgress;
-            }
-            shouldPost = false;
-        }
-
-        ObjectAnimator driftAndAlpha = ObjectAnimator.ofFloat(this, "progress",
-                mProgress, 1f);
-        driftAndAlpha.setDuration(mAnimationDuration);
-        driftAndAlpha.setInterpolator(interpolator);
-        animationOut.play(driftAndAlpha);
-
-        animationOut.addListener(new AnimatorListenerAdapter() {
-            boolean canceled = false;
-
-            @Override
-            public void onAnimationCancel(Animator animation) {
-                canceled = true;
-            }
-
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                if (canceled) {
-                    return;
-                } else {
-                    finishPullDown();
-                    cleanUpAnimation();
-                    mDetector.finishedScrolling();
-                }
-            }
-        });
-        mCurrentAnimation = animationOut;
-        return shouldPost;
-    }
-
-    public void finishPullUp() {
-        mHotseat.setVisibility(View.INVISIBLE);
-        if (hasSpringAnimationHandler()) {
-            mSpringAnimationHandler.remove(mSearchSpring);
-            mSpringAnimationHandler.reset();
-        }
-        setProgress(0f);
-    }
-
-    public void finishPullDown() {
-        mAppsView.setVisibility(View.INVISIBLE);
-        mHotseat.setBackgroundTransparent(false /* transparent */);
-        mHotseat.setVisibility(View.VISIBLE);
-        mAppsView.reset();
-        if (hasSpringAnimationHandler()) {
-            mSpringAnimationHandler.reset();
-        }
-        setProgress(1f);
-    }
-
-    private void cancelAnimation() {
-        if (mCurrentAnimation != null) {
-            mCurrentAnimation.cancel();
-            mCurrentAnimation = null;
-        }
-        cancelDiscoveryAnimation();
-    }
-
     public void cancelDiscoveryAnimation() {
         if (mDiscoBounceAnimation == null) {
             return;
@@ -535,24 +247,12 @@
         mDiscoBounceAnimation = null;
     }
 
-    private void cleanUpAnimation() {
-        mCurrentAnimation = null;
-    }
-
     public void setupViews(AllAppsContainerView appsView, Hotseat hotseat, Workspace workspace) {
         mAppsView = appsView;
         mHotseat = hotseat;
         mWorkspace = workspace;
         mHotseat.bringToFront();
-        mCaretController = new AllAppsCaretController(
-                mWorkspace.getPageIndicator().getCaretDrawable(), mLauncher);
         mAppsView.getSearchUiManager().addOnScrollRangeChangeListener(this);
-        mSpringAnimationHandler = mAppsView.getSpringAnimationHandler();
-        mSearchSpring = mAppsView.getSearchUiManager().getSpringForFling();
-    }
-
-    private boolean hasSpringAnimationHandler() {
-        return FeatureFlags.LAUNCHER3_PHYSICS && mSpringAnimationHandler != null;
     }
 
     @Override
@@ -560,4 +260,19 @@
         mShiftRange = scrollRange;
         setProgress(mProgress);
     }
+
+    /**
+     * Set the final view states based on the progress.
+     * TODO: This logic should go in {@link LauncherState}
+     */
+    private void onProgressAnimationEnd() {
+        if (Float.compare(mProgress, 1f) == 0) {
+            mAppsView.setVisibility(View.INVISIBLE);
+            mHotseat.setVisibility(View.VISIBLE);
+            mAppsView.reset();
+        } else if (Float.compare(mProgress, 0f) == 0) {
+            mHotseat.setVisibility(View.INVISIBLE);
+            mAppsView.setVisibility(View.VISIBLE);
+        }
+    }
 }
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index 6bbe3ea..f9dde2f 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -30,6 +30,7 @@
 import com.android.launcher3.discovery.AppDiscoveryUpdateState;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.ComponentKeyMapper;
+import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.LabelComparator;
 
 import java.util.ArrayList;
@@ -165,7 +166,7 @@
 
     // The set of apps from the system not including predictions
     private final List<AppInfo> mApps = new ArrayList<>();
-    private final HashMap<ComponentKey, AppInfo> mComponentToAppMap = new HashMap<>();
+    private final HashMap<ComponentKey, AppInfo> mComponentToAppMap;
 
     // The set of filtered apps with the current filter
     private final List<AppInfo> mFilteredApps = new ArrayList<>();
@@ -188,13 +189,20 @@
     private int mNumAppsPerRow;
     private int mNumPredictedAppsPerRow;
     private int mNumAppRowsInAdapter;
+    private ItemInfoMatcher mItemFilter;
 
-    public AlphabeticalAppsList(Context context) {
+    public AlphabeticalAppsList(Context context, HashMap<ComponentKey, AppInfo> componentToAppMap) {
+        mComponentToAppMap = componentToAppMap;
         mLauncher = Launcher.getLauncher(context);
         mIndexer = new AlphabeticIndexCompat(context);
         mAppNameComparator = new AppInfoComparator(context);
     }
 
+    public void updateItemFilter(ItemInfoMatcher itemFilter) {
+        this.mItemFilter = itemFilter;
+        onAppsUpdated();
+    }
+
     /**
      * Sets the number of apps per row.
      */
@@ -362,49 +370,30 @@
         int size = apps.size();
         for (int i = 0; i < size; ++i) {
             AppInfo info = apps.get(i);
-            AdapterItem appItem = AdapterItem.asPredictedApp(i, "", info, i);
-            appItem.rowAppIndex = i;
-            mAdapterItems.set(i, appItem);
+            AdapterItem orgItem = mAdapterItems.get(i);
+            AdapterItem newItem = AdapterItem.asPredictedApp(orgItem.position, "", info,
+                    orgItem.appIndex);
+            newItem.rowAppIndex = orgItem.rowAppIndex;
+
+            mAdapterItems.set(i, newItem);
             mFilteredApps.set(i, info);
             mAdapter.notifyItemChanged(i);
         }
     }
 
     /**
-     * Sets the current set of apps.
-     */
-    public void setApps(List<AppInfo> apps) {
-        mComponentToAppMap.clear();
-        addOrUpdateApps(apps);
-    }
-
-    /**
-     * Adds or updates existing apps in the list
-     */
-    public void addOrUpdateApps(List<AppInfo> apps) {
-        for (AppInfo app : apps) {
-            mComponentToAppMap.put(app.toComponentKey(), app);
-        }
-        onAppsUpdated();
-    }
-
-    /**
-     * Removes some apps from the list.
-     */
-    public void removeApps(List<AppInfo> apps) {
-        for (AppInfo app : apps) {
-            mComponentToAppMap.remove(app.toComponentKey());
-        }
-        onAppsUpdated();
-    }
-
-    /**
      * Updates internals when the set of apps are updated.
      */
-    private void onAppsUpdated() {
+    void onAppsUpdated() {
         // Sort the list of apps
         mApps.clear();
-        mApps.addAll(mComponentToAppMap.values());
+
+        for (AppInfo app : mComponentToAppMap.values()) {
+            if (mItemFilter == null || mItemFilter.matches(app, null)) {
+                mApps.add(app);
+            }
+        }
+
         Collections.sort(mApps, mAppNameComparator);
 
         // As a special case for some languages (currently only Simplified Chinese), we may need to
@@ -471,42 +460,45 @@
         mFastScrollerSections.clear();
         mAdapterItems.clear();
 
-        if (DEBUG_PREDICTIONS) {
-            if (mPredictedAppComponents.isEmpty() && !mApps.isEmpty()) {
-                mPredictedAppComponents.add(new ComponentKeyMapper<AppInfo>(new ComponentKey(mApps.get(0).componentName,
-                        Process.myUserHandle())));
-                mPredictedAppComponents.add(new ComponentKeyMapper<AppInfo>(new ComponentKey(mApps.get(0).componentName,
-                        Process.myUserHandle())));
-                mPredictedAppComponents.add(new ComponentKeyMapper<AppInfo>(new ComponentKey(mApps.get(0).componentName,
-                        Process.myUserHandle())));
-                mPredictedAppComponents.add(new ComponentKeyMapper<AppInfo>(new ComponentKey(mApps.get(0).componentName,
-                        Process.myUserHandle())));
-            }
-        }
-
-        // Process the predicted app components
-        mPredictedApps.clear();
-        if (mPredictedAppComponents != null && !mPredictedAppComponents.isEmpty() && !hasFilter()) {
-            mPredictedApps.addAll(processPredictedAppComponents(mPredictedAppComponents));
-
-            if (!mPredictedApps.isEmpty()) {
-                // Add a section for the predictions
-                lastFastScrollerSectionInfo = new FastScrollSectionInfo("");
-                mFastScrollerSections.add(lastFastScrollerSectionInfo);
-
-                // Add the predicted app items
-                for (AppInfo info : mPredictedApps) {
-                    AdapterItem appItem = AdapterItem.asPredictedApp(position++, "", info,
-                            appIndex++);
-                    if (lastFastScrollerSectionInfo.fastScrollToItem == null) {
-                        lastFastScrollerSectionInfo.fastScrollToItem = appItem;
-                    }
-                    mAdapterItems.add(appItem);
-                    mFilteredApps.add(info);
+        if (!FeatureFlags.ALL_APPS_TABS_ENABLED) {
+            if (DEBUG_PREDICTIONS) {
+                if (mPredictedAppComponents.isEmpty() && !mApps.isEmpty()) {
+                    mPredictedAppComponents.add(new ComponentKeyMapper<AppInfo>(new ComponentKey(mApps.get(0).componentName,
+                            Process.myUserHandle())));
+                    mPredictedAppComponents.add(new ComponentKeyMapper<AppInfo>(new ComponentKey(mApps.get(0).componentName,
+                            Process.myUserHandle())));
+                    mPredictedAppComponents.add(new ComponentKeyMapper<AppInfo>(new ComponentKey(mApps.get(0).componentName,
+                            Process.myUserHandle())));
+                    mPredictedAppComponents.add(new ComponentKeyMapper<AppInfo>(new ComponentKey(mApps.get(0).componentName,
+                            Process.myUserHandle())));
                 }
-
-                mAdapterItems.add(AdapterItem.asPredictionDivider(position++));
             }
+
+            // Process the predicted app components
+            mPredictedApps.clear();
+            if (mPredictedAppComponents != null && !mPredictedAppComponents.isEmpty() && !hasFilter()) {
+                mPredictedApps.addAll(processPredictedAppComponents(mPredictedAppComponents));
+
+                if (!mPredictedApps.isEmpty()) {
+                    // Add a section for the predictions
+                    lastFastScrollerSectionInfo = new FastScrollSectionInfo("");
+                    mFastScrollerSections.add(lastFastScrollerSectionInfo);
+
+                    // Add the predicted app items
+                    for (AppInfo info : mPredictedApps) {
+                        AdapterItem appItem = AdapterItem.asPredictedApp(position++, "", info,
+                                appIndex++);
+                        if (lastFastScrollerSectionInfo.fastScrollToItem == null) {
+                            lastFastScrollerSectionInfo.fastScrollToItem = appItem;
+                        }
+                        mAdapterItems.add(appItem);
+                        mFilteredApps.add(info);
+                    }
+
+                    mAdapterItems.add(AdapterItem.asPredictionDivider(position++));
+                }
+            }
+
         }
 
         // Recreate the filtered and sectioned apps (for convenience for the grid layout) from the
@@ -623,7 +615,6 @@
         if (mSearchResults == null) {
             return mApps;
         }
-
         ArrayList<AppInfo> result = new ArrayList<>();
         for (ComponentKey key : mSearchResults) {
             AppInfo match = mComponentToAppMap.get(key);
@@ -645,10 +636,6 @@
         return result;
     }
 
-    public AppInfo findApp(ComponentKeyMapper<AppInfo> mapper) {
-        return mapper.getItem(mComponentToAppMap);
-    }
-
     /**
      * Returns the cached section name for the given title, recomputing and updating the cache if
      * the title has no cached section name.
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderHandler.java b/src/com/android/launcher3/allapps/FloatingHeaderHandler.java
new file mode 100644
index 0000000..984966b
--- /dev/null
+++ b/src/com/android/launcher3/allapps/FloatingHeaderHandler.java
@@ -0,0 +1,139 @@
+/*
+ * 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.allapps;
+
+import android.graphics.Rect;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+
+import com.android.launcher3.R;
+
+public class FloatingHeaderHandler extends RecyclerView.OnScrollListener {
+
+    private final int mMaxTranslation;
+    private final View mHeaderView;
+    private final PredictionRowView mContentView;
+    private final RecyclerView mMainRV;
+    private final RecyclerView mWorkRV;
+    private final Rect mClip = new Rect(0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE);
+
+    private boolean mHeaderHidden;
+    private int mSnappedScrolledY;
+    private int mTranslationY;
+    private int mMainScrolledY;
+    private int mWorkScrolledY;
+    private boolean mMainRVActive;
+
+    public FloatingHeaderHandler(@NonNull View header, @NonNull RecyclerView personalRV,
+            @Nullable RecyclerView workRV, int contentHeight) {
+        mHeaderView = header;
+        mContentView = mHeaderView.findViewById(R.id.header_content);
+        mContentView.getLayoutParams().height = contentHeight;
+        mMaxTranslation = contentHeight;
+        mMainRV = personalRV;
+        mMainRV.addOnScrollListener(this);
+        mWorkRV = workRV;
+        if (workRV != null) {
+            workRV.addOnScrollListener(this);
+        }
+        setMainActive(true);
+    }
+
+    public void setMainActive(boolean active) {
+        mMainRVActive = active;
+        mSnappedScrolledY = getCurrentScroll() - mMaxTranslation;
+        setExpanded(true);
+    }
+
+    public View getHeaderView() {
+        return mHeaderView;
+    }
+
+    public PredictionRowView getContentView() {
+        return mContentView;
+    }
+
+    @Override
+    public void onScrolled(RecyclerView rv, int dx, int dy) {
+        boolean isMainRV = rv == mMainRV;
+        if (isMainRV != mMainRVActive) {
+            return;
+        }
+
+        int current = isMainRV
+                ? (mMainScrolledY -= dy)
+                : (mWorkScrolledY -= dy);
+
+        if (dy == 0) {
+            setExpanded(true);
+        } else {
+            moved(current);
+            apply();
+        }
+    }
+
+    private void moved(final int currentScrollY) {
+        if (mHeaderHidden) {
+            if (currentScrollY <= mSnappedScrolledY) {
+                mSnappedScrolledY = currentScrollY;
+            } else {
+                mHeaderHidden = false;
+            }
+            mTranslationY = currentScrollY;
+        } else {
+            mTranslationY = currentScrollY - mSnappedScrolledY - mMaxTranslation;
+
+            // update state vars
+            if (mTranslationY >= 0) { // expanded: must not move down further
+                mTranslationY = 0;
+                mSnappedScrolledY = currentScrollY - mMaxTranslation;
+            } else if (mTranslationY <= -mMaxTranslation) { // hide or stay hidden
+                mHeaderHidden = true;
+                mSnappedScrolledY = currentScrollY;
+            }
+        }
+    }
+
+    private void apply() {
+        mTranslationY = Math.max(mTranslationY, -mMaxTranslation);
+        mHeaderView.setTranslationY(mTranslationY);
+        mClip.top = mMaxTranslation + mTranslationY;
+        mMainRV.setClipBounds(mClip);
+        if (mWorkRV != null) {
+            mWorkRV.setClipBounds(mClip);
+        }
+    }
+
+    private void setExpanded(boolean expand) {
+        int translateTo = expand ? 0 : -mMaxTranslation;
+        mTranslationY = translateTo;
+        apply();
+
+        mHeaderHidden = !expand;
+        mSnappedScrolledY = expand ? getCurrentScroll() - mMaxTranslation : getCurrentScroll();
+    }
+
+    public boolean isExpanded() {
+        return !mHeaderHidden;
+    }
+
+    private int getCurrentScroll() {
+        return mMainRVActive ? mMainScrolledY : mWorkScrolledY;
+    }
+
+}
diff --git a/src/com/android/launcher3/allapps/InterceptingViewPager.java b/src/com/android/launcher3/allapps/InterceptingViewPager.java
new file mode 100644
index 0000000..3524ca9
--- /dev/null
+++ b/src/com/android/launcher3/allapps/InterceptingViewPager.java
@@ -0,0 +1,91 @@
+/*
+ * 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.allapps;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.view.ViewPager;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+import com.android.launcher3.touch.SwipeDetector;
+
+import static android.view.MotionEvent.INVALID_POINTER_ID;
+
+
+public class InterceptingViewPager extends ViewPager {
+
+
+    private final PointF mDownPos = new PointF();
+    private final PointF mLastPos = new PointF();
+    private final int mSlop;
+    private int mActivePointerId = INVALID_POINTER_ID;
+
+    public InterceptingViewPager(@NonNull Context context) {
+        super(context);
+        mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
+    }
+
+    public InterceptingViewPager(@NonNull Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+        mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        boolean result = super.onInterceptTouchEvent(ev);
+        if (!result) {
+            switch (ev.getActionMasked()) {
+                case MotionEvent.ACTION_DOWN:
+                    mActivePointerId = ev.getPointerId(0);
+                    mDownPos.set(ev.getX(), ev.getY());
+                    mLastPos.set(mDownPos);
+                    break;
+                case MotionEvent.ACTION_POINTER_UP:
+                    int ptrIdx = ev.getActionIndex();
+                    int ptrId = ev.getPointerId(ptrIdx);
+                    if (ptrId == mActivePointerId) {
+                        final int newPointerIdx = ptrIdx == 0 ? 1 : 0;
+                        mDownPos.set(
+                                ev.getX(newPointerIdx) - (mLastPos.x - mDownPos.x),
+                                ev.getY(newPointerIdx) - (mLastPos.y - mDownPos.y));
+                        mLastPos.set(ev.getX(newPointerIdx), ev.getY(newPointerIdx));
+                        mActivePointerId = ev.getPointerId(newPointerIdx);
+                    }
+                    break;
+                case MotionEvent.ACTION_MOVE:
+                    int pointerIndex = ev.findPointerIndex(mActivePointerId);
+                    if (pointerIndex == INVALID_POINTER_ID) {
+                        break;
+                    }
+                    float deltaX = ev.getX() - mDownPos.x;
+                    float deltaY = ev.getY() - mDownPos.y;
+                    if (Math.abs(deltaX) > mSlop && Math.abs(deltaX) > Math.abs(deltaY)) {
+                        return true;
+                    }
+                    mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
+                    break;
+                default:
+                    break;
+            }
+        }
+        return result;
+    }
+
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/allapps/PredictionRowView.java b/src/com/android/launcher3/allapps/PredictionRowView.java
new file mode 100644
index 0000000..5551f07
--- /dev/null
+++ b/src/com/android/launcher3/allapps/PredictionRowView.java
@@ -0,0 +1,139 @@
+/*
+ * 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.allapps;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.widget.LinearLayout;
+
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.ComponentKeyMapper;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+
+public class PredictionRowView extends LinearLayout {
+
+    private static final String TAG = "PredictionRowView";
+
+    private HashMap<ComponentKey, AppInfo> mComponentToAppMap;
+    private int mNumPredictedAppsPerRow;
+    // The set of predicted app component names
+    private final List<ComponentKeyMapper<AppInfo>> mPredictedAppComponents = new ArrayList<>();
+    // The set of predicted apps resolved from the component names and the current set of apps
+    private final List<AppInfo> mPredictedApps = new ArrayList<>();
+
+    public PredictionRowView(@NonNull Context context) {
+        this(context, null);
+    }
+
+    public PredictionRowView(@NonNull Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+        setOrientation(LinearLayout.HORIZONTAL);
+    }
+
+    public void setComponentToAppMap(HashMap<ComponentKey, AppInfo> componentToAppMap) {
+        this.mComponentToAppMap = componentToAppMap;
+    }
+
+    /**
+     * Sets the number of apps per row.
+     */
+    public void setNumAppsPerRow(int numPredictedAppsPerRow) {
+        mNumPredictedAppsPerRow = numPredictedAppsPerRow;
+    }
+
+    public void onAppsUpdated() {
+        // TODO
+    }
+
+    /**
+     * Returns the predicted apps.
+     */
+    public List<AppInfo> getPredictedApps() {
+        return mPredictedApps;
+    }
+
+    /**
+     * Sets the current set of predicted apps.
+     *
+     * This can be called before we get the full set of applications, we should merge the results
+     * only in onAppsUpdated() which is idempotent.
+     *
+     * If the number of predicted apps is the same as the previous list of predicted apps,
+     * we can optimize by swapping them in place.
+     */
+    public void setPredictedApps(List<ComponentKeyMapper<AppInfo>> apps) {
+        mPredictedAppComponents.clear();
+        mPredictedAppComponents.addAll(apps);
+
+        List<AppInfo> newPredictedApps = processPredictedAppComponents(apps);
+        // We only need to do work if any of the visible predicted apps have changed.
+        if (!newPredictedApps.equals(mPredictedApps)) {
+            if (newPredictedApps.size() == mPredictedApps.size()) {
+                swapInNewPredictedApps(newPredictedApps);
+            } else {
+                // We need to update the appIndex of all the items.
+                onAppsUpdated();
+            }
+        }
+    }
+
+    private List<AppInfo> processPredictedAppComponents(
+            List<ComponentKeyMapper<AppInfo>> components) {
+        if (mComponentToAppMap.isEmpty()) {
+            // Apps have not been bound yet.
+            return Collections.emptyList();
+        }
+
+        List<AppInfo> predictedApps = new ArrayList<>();
+        for (ComponentKeyMapper<AppInfo> mapper : components) {
+            AppInfo info = mapper.getItem(mComponentToAppMap);
+            if (info != null) {
+                predictedApps.add(info);
+            } else {
+                if (FeatureFlags.IS_DOGFOOD_BUILD) {
+                    Log.e(TAG, "Predicted app not found: " + mapper);
+                }
+            }
+            // Stop at the number of predicted apps
+            if (predictedApps.size() == mNumPredictedAppsPerRow) {
+                break;
+            }
+        }
+        return predictedApps;
+    }
+
+    /**
+     * Swaps out the old predicted apps with the new predicted apps, in place. This optimization
+     * allows us to skip an entire relayout that would otherwise be called by notifyDataSetChanged.
+     *
+     * Note: This should only be called if the # of predicted apps is the same.
+     *       This method assumes that predicted apps are the first items in the adapter.
+     */
+    private void swapInNewPredictedApps(List<AppInfo> apps) {
+        // TODO
+    }
+
+}
diff --git a/src/com/android/launcher3/allapps/SearchUiManager.java b/src/com/android/launcher3/allapps/SearchUiManager.java
index 34230e0..f562b6a 100644
--- a/src/com/android/launcher3/allapps/SearchUiManager.java
+++ b/src/com/android/launcher3/allapps/SearchUiManager.java
@@ -27,7 +27,7 @@
     /**
      * Initializes the search manager.
      */
-    void initialize(AlphabeticalAppsList appsList, AllAppsRecyclerView recyclerView);
+    void initialize(AllAppsContainerView containerView);
 
     /**
      * A {@link SpringAnimation} that will be used when the user flings.
diff --git a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
index 63aa7be..bf03a0e 100644
--- a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
+++ b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
@@ -15,16 +15,13 @@
  */
 package com.android.launcher3.allapps.search;
 
-import android.content.Context;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.text.Editable;
 import android.text.TextUtils;
 import android.text.TextWatcher;
 import android.view.KeyEvent;
-import android.view.View;
 import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputMethodManager;
 import android.widget.TextView;
 import android.widget.TextView.OnEditorActionListener;
 
@@ -50,7 +47,6 @@
     protected String mQuery;
 
     protected SearchAlgorithm mSearchAlgorithm;
-    protected InputMethodManager mInputMethodManager;
 
     public void setVisibility(int visibility) {
         mInput.setVisibility(visibility);
@@ -68,10 +64,6 @@
         mInput.addTextChangedListener(this);
         mInput.setOnEditorActionListener(this);
         mInput.setOnBackKeyListener(this);
-
-        mInputMethodManager = (InputMethodManager)
-                mInput.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
-
         mSearchAlgorithm = searchAlgorithm;
     }
 
@@ -137,22 +129,9 @@
      * Resets the search bar state.
      */
     public void reset() {
-        unfocusSearchField();
         mCb.clearSearchResult();
-        mInput.setText("");
+        mInput.reset();
         mQuery = null;
-        hideKeyboard();
-    }
-
-    protected void hideKeyboard() {
-        mInputMethodManager.hideSoftInputFromWindow(mInput.getWindowToken(), 0);
-    }
-
-    protected void unfocusSearchField() {
-        View nextFocus = mInput.focusSearch(View.FOCUS_DOWN);
-        if (nextFocus != null) {
-            nextFocus.requestFocus();
-        }
     }
 
     /**
diff --git a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
index ddf6e58..e65a2c4 100644
--- a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
+++ b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
@@ -31,19 +31,19 @@
 import android.view.KeyEvent;
 import android.view.View;
 import android.widget.FrameLayout;
+
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.ExtendedEditText;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
-import com.android.launcher3.allapps.AllAppsGridAdapter;
-import com.android.launcher3.allapps.AllAppsRecyclerView;
+import com.android.launcher3.allapps.AllAppsContainerView;
 import com.android.launcher3.allapps.AlphabeticalAppsList;
 import com.android.launcher3.allapps.SearchUiManager;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.discovery.AppDiscoveryItem;
 import com.android.launcher3.discovery.AppDiscoveryUpdateState;
 import com.android.launcher3.graphics.TintedDrawableSpan;
 import com.android.launcher3.util.ComponentKey;
+
 import java.util.ArrayList;
 
 /**
@@ -60,11 +60,9 @@
 
     private ExtendedEditText mSearchInput;
     private AlphabeticalAppsList mApps;
-    private AllAppsRecyclerView mAppsRecyclerView;
-    private AllAppsGridAdapter mAdapter;
     private View mDivider;
     private HeaderElevationController mElevationController;
-
+    private AllAppsContainerView mAppsView;
     private SpringAnimation mSpring;
 
     public AppsSearchContainerLayout(Context context) {
@@ -124,14 +122,12 @@
 
 
     @Override
-    public void initialize(
-            AlphabeticalAppsList appsList, AllAppsRecyclerView recyclerView) {
-        mApps = appsList;
-        mAppsRecyclerView = recyclerView;
-        mAppsRecyclerView.addOnScrollListener(mElevationController);
-        mAdapter = (AllAppsGridAdapter) mAppsRecyclerView.getAdapter();
+    public void initialize(AllAppsContainerView appsView) {
+        mApps = appsView.getApps();
+        mAppsView = appsView;
+        appsView.addElevationController(mElevationController);
         mSearchBarController.initialize(
-                new DefaultAppSearchAlgorithm(appsList.getApps()), mSearchInput, mLauncher, this);
+                new DefaultAppSearchAlgorithm(mApps.getApps()), mSearchInput, mLauncher, this);
     }
 
     @Override
@@ -174,7 +170,7 @@
         if (apps != null) {
             mApps.setOrderedFilter(apps);
             notifyResultChanged();
-            mAdapter.setLastSearchQuery(query);
+            mAppsView.setLastSearchQuery(query);
         }
     }
 
@@ -201,7 +197,7 @@
 
     private void notifyResultChanged() {
         mElevationController.reset();
-        mAppsRecyclerView.onSearchResultsChanged();
+        mAppsView.onSearchResultsChanged();
     }
 
     @Override
diff --git a/src/com/android/launcher3/anim/AnimationSuccessListener.java b/src/com/android/launcher3/anim/AnimationSuccessListener.java
new file mode 100644
index 0000000..9448632
--- /dev/null
+++ b/src/com/android/launcher3/anim/AnimationSuccessListener.java
@@ -0,0 +1,42 @@
+/*
+ * 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.anim;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+
+/**
+ * Extension of {@link AnimatorListenerAdapter} for listening for non-cancelled animations
+ */
+public abstract class AnimationSuccessListener extends AnimatorListenerAdapter {
+
+    protected boolean mCancelled = false;
+
+    @Override
+    public void onAnimationCancel(Animator animation) {
+        mCancelled = true;
+    }
+
+    @Override
+    public void onAnimationEnd(Animator animation) {
+        if (!mCancelled) {
+            onAnimationSuccess(animation);
+        }
+    }
+
+    public abstract void onAnimationSuccess(Animator animator);
+}
diff --git a/src/com/android/launcher3/anim/AnimatorPlaybackController.java b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
new file mode 100644
index 0000000..826a20e
--- /dev/null
+++ b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
@@ -0,0 +1,216 @@
+/*
+ * 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.anim;
+
+import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
+import android.animation.AnimatorSet;
+import android.animation.ValueAnimator;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Helper class to control the playback of an {@link AnimatorSet}, with custom interpolators
+ * and durations.
+ *
+ * Note: The implementation does not support start delays on child animations or
+ * sequential playbacks.
+ */
+public abstract class AnimatorPlaybackController implements ValueAnimator.AnimatorUpdateListener {
+
+    public static AnimatorPlaybackController wrap(AnimatorSet anim, long duration) {
+
+        /**
+         * TODO: use {@link AnimatorSet#setCurrentPlayTime(long)} once b/68382377 is fixed.
+         */
+        return new AnimatorPlaybackControllerVL(anim, duration);
+    }
+
+    private final ValueAnimator mAnimationPlayer;
+    private final long mDuration;
+
+    protected final AnimatorSet mAnim;
+
+    protected float mCurrentFraction;
+    private Runnable mEndAction;
+
+    protected AnimatorPlaybackController(AnimatorSet anim, long duration) {
+        mAnim = anim;
+        mDuration = duration;
+
+        mAnimationPlayer = ValueAnimator.ofFloat(0, 1);
+        mAnimationPlayer.setInterpolator(Interpolators.LINEAR);
+        mAnimationPlayer.addListener(new OnAnimationEndDispatcher());
+        mAnimationPlayer.addUpdateListener(this);
+    }
+
+    public AnimatorSet getTarget() {
+        return mAnim;
+    }
+
+    /**
+     * Starts playing the animation forward from current position.
+     */
+    public void start() {
+        mAnimationPlayer.setFloatValues(mCurrentFraction, 1);
+        mAnimationPlayer.setDuration(clampDuration(1 - mCurrentFraction));
+        mAnimationPlayer.start();
+    }
+
+    /**
+     * Starts playing the animation backwards from current position
+     */
+    public void reverse() {
+        mAnimationPlayer.setFloatValues(mCurrentFraction, 0);
+        mAnimationPlayer.setDuration(clampDuration(mCurrentFraction));
+        mAnimationPlayer.start();
+    }
+
+    /**
+     * Pauses the currently playing animation.
+     */
+    public void pause() {
+        mAnimationPlayer.cancel();
+    }
+
+    /**
+     * Returns the underlying animation used for controlling the set.
+     */
+    public ValueAnimator getAnimationPlayer() {
+        return mAnimationPlayer;
+    }
+
+    /**
+     * Sets the current animation position and updates all the child animators accordingly.
+     */
+    public abstract void setPlayFraction(float fraction);
+
+    public float getProgressFraction() {
+        return mCurrentFraction;
+    }
+
+    /**
+     * Sets the action to be called when the animation is completed. Also clears any
+     * previously set action.
+     */
+    public void setEndAction(Runnable runnable) {
+        mEndAction = runnable;
+    }
+
+    @Override
+    public void onAnimationUpdate(ValueAnimator valueAnimator) {
+        setPlayFraction((float) valueAnimator.getAnimatedValue());
+    }
+
+    protected long clampDuration(float fraction) {
+        float playPos = mDuration * fraction;
+        if (playPos <= 0) {
+            return 0;
+        } else {
+            return Math.min((long) playPos, mDuration);
+        }
+    }
+
+    public void dispatchOnStart() {
+        dispatchOnStartRecursively(mAnim);
+    }
+
+    private void dispatchOnStartRecursively(Animator animator) {
+        for (AnimatorListener l : nonNullList(animator.getListeners())) {
+            l.onAnimationStart(animator);
+        }
+
+        if (animator instanceof AnimatorSet) {
+            for (Animator anim : nonNullList(((AnimatorSet) animator).getChildAnimations())) {
+                dispatchOnStartRecursively(anim);
+            }
+        }
+    }
+
+    public static class AnimatorPlaybackControllerVL extends AnimatorPlaybackController {
+
+        private final ValueAnimator[] mChildAnimations;
+
+        private AnimatorPlaybackControllerVL(AnimatorSet anim, long duration) {
+            super(anim, duration);
+
+            // Build animation list
+            ArrayList<ValueAnimator> childAnims = new ArrayList<>();
+            getAnimationsRecur(mAnim, childAnims);
+            mChildAnimations = childAnims.toArray(new ValueAnimator[childAnims.size()]);
+        }
+
+        private void getAnimationsRecur(AnimatorSet anim, ArrayList<ValueAnimator> out) {
+            long forceDuration = anim.getDuration();
+            for (Animator child : anim.getChildAnimations()) {
+                if (forceDuration > 0) {
+                    child.setDuration(forceDuration);
+                }
+                if (child instanceof ValueAnimator) {
+                    out.add((ValueAnimator) child);
+                } else if (child instanceof AnimatorSet) {
+                    getAnimationsRecur((AnimatorSet) child, out);
+                } else {
+                    throw new RuntimeException("Unknown animation type " + child);
+                }
+            }
+        }
+
+        @Override
+        public void setPlayFraction(float fraction) {
+            mCurrentFraction = fraction;
+            long playPos = clampDuration(fraction);
+            for (ValueAnimator anim : mChildAnimations) {
+                anim.setCurrentPlayTime(Math.min(playPos, anim.getDuration()));
+            }
+        }
+
+    }
+
+    private class OnAnimationEndDispatcher extends AnimationSuccessListener {
+
+        @Override
+        public void onAnimationStart(Animator animation) {
+            mCancelled = false;
+        }
+
+        @Override
+        public void onAnimationSuccess(Animator animator) {
+            dispatchOnEndRecursively(mAnim);
+            if (mEndAction != null) {
+                mEndAction.run();
+            }
+        }
+
+        private void dispatchOnEndRecursively(Animator animator) {
+            for (AnimatorListener l : nonNullList(animator.getListeners())) {
+                l.onAnimationEnd(animator);
+            }
+
+            if (animator instanceof AnimatorSet) {
+                for (Animator anim : nonNullList(((AnimatorSet) animator).getChildAnimations())) {
+                    dispatchOnEndRecursively(anim);
+                }
+            }
+        }
+    }
+
+    private static <T> List<T> nonNullList(ArrayList<T> list) {
+        return list == null ? Collections.<T>emptyList() : list;
+    }
+}
diff --git a/src/com/android/launcher3/anim/CircleRevealOutlineProvider.java b/src/com/android/launcher3/anim/CircleRevealOutlineProvider.java
deleted file mode 100644
index 9fb6b49..0000000
--- a/src/com/android/launcher3/anim/CircleRevealOutlineProvider.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2016 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.anim;
-
-public class CircleRevealOutlineProvider extends RevealOutlineAnimation {
-
-    private int mCenterX;
-    private int mCenterY;
-    private float mRadius0;
-    private float mRadius1;
-
-    /**
-     * @param x reveal center x
-     * @param y reveal center y
-     * @param r0 initial radius
-     * @param r1 final radius
-     */
-    public CircleRevealOutlineProvider(int x, int y, float r0, float r1) {
-        mCenterX = x;
-        mCenterY = y;
-        mRadius0 = r0;
-        mRadius1 = r1;
-    }
-
-    @Override
-    public boolean shouldRemoveElevationDuringAnimation() {
-        return true;
-    }
-
-    @Override
-    public void setProgress(float progress) {
-        mOutlineRadius = (1 - progress) * mRadius0 + progress * mRadius1;
-
-        mOutline.left = (int) (mCenterX - mOutlineRadius);
-        mOutline.top = (int) (mCenterY - mOutlineRadius);
-        mOutline.right = (int) (mCenterX + mOutlineRadius);
-        mOutline.bottom = (int) (mCenterY + mOutlineRadius);
-    }
-}
diff --git a/src/com/android/launcher3/anim/Interpolators.java b/src/com/android/launcher3/anim/Interpolators.java
new file mode 100644
index 0000000..8826e64
--- /dev/null
+++ b/src/com/android/launcher3/anim/Interpolators.java
@@ -0,0 +1,90 @@
+/*
+ * 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.anim;
+
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.view.animation.LinearInterpolator;
+import android.view.animation.PathInterpolator;
+
+
+/**
+ * Common interpolators used in Launcher
+ */
+public class Interpolators {
+
+    public static final Interpolator LINEAR = new LinearInterpolator();
+
+    public static final Interpolator ACCEL = new AccelerateInterpolator();
+    public static final Interpolator ACCEL_1_5 = new AccelerateInterpolator(1.5f);
+    public static final Interpolator ACCEL_2 = new AccelerateInterpolator(2);
+
+    public static final Interpolator DEACCEL = new DecelerateInterpolator();
+    public static final Interpolator DEACCEL_1_5 = new DecelerateInterpolator(1.5f);
+    public static final Interpolator DEACCEL_2 = new DecelerateInterpolator(2);
+    public static final Interpolator DEACCEL_2_5 = new DecelerateInterpolator(2.5f);
+    public static final Interpolator DEACCEL_3 = new DecelerateInterpolator(3f);
+
+    public static final Interpolator FAST_OUT_SLOW_IN = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
+
+    /**
+     * Inversion of zInterpolate, compounded with an ease-out.
+     */
+    public static final Interpolator ZOOM_IN = new Interpolator() {
+
+        private static final float FOCAL_LENGTH = 0.35f;
+
+        @Override
+        public float getInterpolation(float v) {
+            return DEACCEL_3.getInterpolation(1 - zInterpolate(1 - v));
+        }
+
+        /**
+         * This interpolator emulates the rate at which the perceived scale of an object changes
+         * as its distance from a camera increases. When this interpolator is applied to a scale
+         * animation on a view, it evokes the sense that the object is shrinking due to moving away
+         * from the camera.
+         */
+        private float zInterpolate(float input) {
+            return (1.0f - FOCAL_LENGTH / (FOCAL_LENGTH + input)) /
+                    (1.0f - FOCAL_LENGTH / (FOCAL_LENGTH + 1.0f));
+        }
+    };
+
+    public static final Interpolator SCROLL = new Interpolator() {
+        @Override
+        public float getInterpolation(float t) {
+            t -= 1.0f;
+            return t*t*t*t*t + 1;
+        }
+    };
+
+    public static final Interpolator SCROLL_CUBIC = new Interpolator() {
+        @Override
+        public float getInterpolation(float t) {
+            t -= 1.0f;
+            return t*t*t + 1;
+        }
+    };
+
+    private static final float FAST_FLING_PX_MS = 10;
+
+    public static Interpolator scrollInterpolatorForVelocity(float velocity) {
+        return Math.abs(velocity) > FAST_FLING_PX_MS ? SCROLL : SCROLL_CUBIC;
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/anim/RevealOutlineAnimation.java b/src/com/android/launcher3/anim/RevealOutlineAnimation.java
index 51d00d9..1312da9 100644
--- a/src/com/android/launcher3/anim/RevealOutlineAnimation.java
+++ b/src/com/android/launcher3/anim/RevealOutlineAnimation.java
@@ -28,10 +28,6 @@
     /** Sets the progress, from 0 to 1, of the reveal animation. */
     abstract void setProgress(float progress);
 
-    public ValueAnimator createRevealAnimator(final View revealView) {
-        return createRevealAnimator(revealView, false);
-    }
-
     public ValueAnimator createRevealAnimator(final View revealView, boolean isReversed) {
         ValueAnimator va =
                 isReversed ? ValueAnimator.ofFloat(1f, 0f) : ValueAnimator.ofFloat(0f, 1f);
@@ -39,8 +35,13 @@
 
         va.addListener(new AnimatorListenerAdapter() {
             private boolean mWasCanceled = false;
+            private boolean mIsClippedToOutline;
+            private ViewOutlineProvider mOldOutlineProvider;
 
             public void onAnimationStart(Animator animation) {
+                mIsClippedToOutline = revealView.getClipToOutline();
+                mOldOutlineProvider = revealView.getOutlineProvider();
+
                 revealView.setOutlineProvider(RevealOutlineAnimation.this);
                 revealView.setClipToOutline(true);
                 if (shouldRemoveElevationDuringAnimation()) {
@@ -55,8 +56,8 @@
 
             public void onAnimationEnd(Animator animation) {
                 if (!mWasCanceled) {
-                    revealView.setOutlineProvider(ViewOutlineProvider.BACKGROUND);
-                    revealView.setClipToOutline(false);
+                    revealView.setOutlineProvider(mOldOutlineProvider);
+                    revealView.setClipToOutline(mIsClippedToOutline);
                     if (shouldRemoveElevationDuringAnimation()) {
                         revealView.setTranslationZ(0);
                     }
diff --git a/src/com/android/launcher3/anim/RoundedRectRevealOutlineProvider.java b/src/com/android/launcher3/anim/RoundedRectRevealOutlineProvider.java
index d01b26c..9c09477 100644
--- a/src/com/android/launcher3/anim/RoundedRectRevealOutlineProvider.java
+++ b/src/com/android/launcher3/anim/RoundedRectRevealOutlineProvider.java
@@ -18,11 +18,6 @@
 
 import android.graphics.Rect;
 
-import com.android.launcher3.popup.PopupContainerWithArrow;
-
-import static com.android.launcher3.popup.PopupContainerWithArrow.ROUNDED_BOTTOM_CORNERS;
-import static com.android.launcher3.popup.PopupContainerWithArrow.ROUNDED_TOP_CORNERS;
-
 /**
  * A {@link RevealOutlineAnimation} that provides an outline that interpolates between two radii
  * and two {@link Rect}s.
@@ -37,21 +32,12 @@
     private final Rect mStartRect;
     private final Rect mEndRect;
 
-    private final @PopupContainerWithArrow.RoundedCornerFlags int mRoundedCorners;
-
     public RoundedRectRevealOutlineProvider(float startRadius, float endRadius, Rect startRect,
             Rect endRect) {
-        this(startRadius, endRadius, startRect, endRect,
-                ROUNDED_TOP_CORNERS | ROUNDED_BOTTOM_CORNERS);
-    }
-
-    public RoundedRectRevealOutlineProvider(float startRadius, float endRadius, Rect startRect,
-            Rect endRect, int roundedCorners) {
         mStartRadius = startRadius;
         mEndRadius = endRadius;
         mStartRect = startRect;
         mEndRect = endRect;
-        mRoundedCorners = roundedCorners;
     }
 
     @Override
@@ -65,13 +51,7 @@
 
         mOutline.left = (int) ((1 - progress) * mStartRect.left + progress * mEndRect.left);
         mOutline.top = (int) ((1 - progress) * mStartRect.top + progress * mEndRect.top);
-        if ((mRoundedCorners & ROUNDED_TOP_CORNERS) == 0) {
-            mOutline.top -= mOutlineRadius;
-        }
         mOutline.right = (int) ((1 - progress) * mStartRect.right + progress * mEndRect.right);
         mOutline.bottom = (int) ((1 - progress) * mStartRect.bottom + progress * mEndRect.bottom);
-        if ((mRoundedCorners & ROUNDED_BOTTOM_CORNERS) == 0) {
-            mOutline.bottom += mOutlineRadius;
-        }
     }
 }
diff --git a/src/com/android/launcher3/anim/SpringAnimationHandler.java b/src/com/android/launcher3/anim/SpringAnimationHandler.java
index eec3a48..29a2430 100644
--- a/src/com/android/launcher3/anim/SpringAnimationHandler.java
+++ b/src/com/android/launcher3/anim/SpringAnimationHandler.java
@@ -83,6 +83,10 @@
         mAnimations.add(spring);
     }
 
+    public AnimationFactory<T> getFactory() {
+        return mAnimationFactory;
+    }
+
     /**
      * Adds a new or recycled animation to the list of springs handled by this class.
      *
diff --git a/src/com/android/launcher3/badge/BadgeRenderer.java b/src/com/android/launcher3/badge/BadgeRenderer.java
index c2cc215..6ce334e 100644
--- a/src/com/android/launcher3/badge/BadgeRenderer.java
+++ b/src/com/android/launcher3/badge/BadgeRenderer.java
@@ -26,6 +26,7 @@
 import android.graphics.Rect;
 import android.graphics.Shader;
 import android.support.annotation.Nullable;
+import android.util.Log;
 import android.util.SparseArray;
 
 import com.android.launcher3.R;
@@ -38,6 +39,8 @@
  */
 public class BadgeRenderer {
 
+    private static final String TAG = "BadgeRenderer";
+
     private static final boolean DOTS_ONLY = true;
 
     // The badge sizes are defined as percentages of the app icon size.
@@ -95,6 +98,10 @@
      */
     public void draw(Canvas canvas, IconPalette palette, @Nullable BadgeInfo badgeInfo,
             Rect iconBounds, float badgeScale, Point spaceForOffset) {
+        if (palette == null || iconBounds == null || spaceForOffset == null) {
+            Log.e(TAG, "Invalid null argument(s) passed in call to draw.");
+            return;
+        }
         mTextPaint.setColor(palette.textColor);
         IconDrawer iconDrawer = badgeInfo != null && badgeInfo.isIconLarge()
                 ? mLargeIconDrawer : mSmallIconDrawer;
diff --git a/src/com/android/launcher3/compat/AppWidgetManagerCompat.java b/src/com/android/launcher3/compat/AppWidgetManagerCompat.java
index a77a87f..fd1f0cc 100644
--- a/src/com/android/launcher3/compat/AppWidgetManagerCompat.java
+++ b/src/com/android/launcher3/compat/AppWidgetManagerCompat.java
@@ -24,10 +24,13 @@
 import android.os.UserHandle;
 import android.support.annotation.Nullable;
 
+import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.widget.custom.CustomWidgetParser;
 
 import java.util.HashMap;
 import java.util.List;
@@ -58,12 +61,13 @@
         mAppWidgetManager = AppWidgetManager.getInstance(context);
     }
 
-    public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId) {
-        return mAppWidgetManager.getAppWidgetInfo(appWidgetId);
-    }
-
     public LauncherAppWidgetProviderInfo getLauncherAppWidgetInfo(int appWidgetId) {
-        AppWidgetProviderInfo info = getAppWidgetInfo(appWidgetId);
+        if (FeatureFlags.ENABLE_CUSTOM_WIDGETS
+                && appWidgetId <= LauncherAppWidgetInfo.CUSTOM_WIDGET_ID) {
+            return CustomWidgetParser.getWidgetProvider(mContext, appWidgetId);
+        }
+
+        AppWidgetProviderInfo info = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
         return info == null ? null : LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, info);
     }
 
diff --git a/src/com/android/launcher3/compat/AppWidgetManagerCompatVL.java b/src/com/android/launcher3/compat/AppWidgetManagerCompatVL.java
index cb3bd6c..8430285 100644
--- a/src/com/android/launcher3/compat/AppWidgetManagerCompatVL.java
+++ b/src/com/android/launcher3/compat/AppWidgetManagerCompatVL.java
@@ -20,14 +20,17 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.os.Bundle;
+import android.os.Process;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.support.annotation.Nullable;
 
+import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.widget.custom.CustomWidgetParser;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -54,6 +57,10 @@
             for (UserHandle user : mUserManager.getUserProfiles()) {
                 providers.addAll(mAppWidgetManager.getInstalledProvidersForProfile(user));
             }
+
+            if (FeatureFlags.ENABLE_CUSTOM_WIDGETS) {
+                providers.addAll(CustomWidgetParser.getCustomWidgets(mContext));
+            }
             return providers;
         }
         // Only get providers for the given package/user.
@@ -65,6 +72,11 @@
                 iterator.remove();
             }
         }
+
+        if (FeatureFlags.ENABLE_CUSTOM_WIDGETS && Process.myUserHandle().equals(packageUser.mUser)
+                && mContext.getPackageName().equals(packageUser.mPackageName)) {
+            providers.addAll(CustomWidgetParser.getCustomWidgets(mContext));
+        }
         return providers;
     }
 
@@ -74,6 +86,11 @@
         if (FeatureFlags.GO_DISABLE_WIDGETS) {
             return false;
         }
+
+        if (FeatureFlags.ENABLE_CUSTOM_WIDGETS
+                && appWidgetId <= LauncherAppWidgetInfo.CUSTOM_WIDGET_ID) {
+            return true;
+        }
         return mAppWidgetManager.bindAppWidgetIdIfAllowed(
                 appWidgetId, info.getProfile(), info.provider, options);
     }
@@ -89,6 +106,15 @@
                 return LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, info);
             }
         }
+
+        if (FeatureFlags.ENABLE_CUSTOM_WIDGETS && Process.myUserHandle().equals(user)) {
+            for (LauncherAppWidgetProviderInfo info :
+                    CustomWidgetParser.getCustomWidgets(mContext)) {
+                if (info.provider.equals(provider)) {
+                    return info;
+                }
+            }
+        }
         return null;
     }
 
@@ -104,6 +130,13 @@
                 result.put(new ComponentKey(info.provider, user), info);
             }
         }
+
+        if (FeatureFlags.ENABLE_CUSTOM_WIDGETS) {
+            for (LauncherAppWidgetProviderInfo info :
+                    CustomWidgetParser.getCustomWidgets(mContext)) {
+                result.put(new ComponentKey(info.provider, info.getProfile()), info);
+            }
+        }
         return result;
     }
 }
diff --git a/src/com/android/launcher3/compat/WallpaperManagerCompatVL.java b/src/com/android/launcher3/compat/WallpaperManagerCompatVL.java
index 8e572ee..4cc70d3 100644
--- a/src/com/android/launcher3/compat/WallpaperManagerCompatVL.java
+++ b/src/com/android/launcher3/compat/WallpaperManagerCompatVL.java
@@ -15,6 +15,10 @@
  */
 package com.android.launcher3.compat;
 
+import static android.app.WallpaperManager.FLAG_SYSTEM;
+
+import static com.android.launcher3.Utilities.getDevicePrefs;
+
 import android.app.WallpaperInfo;
 import android.app.WallpaperManager;
 import android.app.job.JobInfo;
@@ -38,7 +42,6 @@
 import android.os.HandlerThread;
 import android.os.ParcelFileDescriptor;
 import android.support.annotation.Nullable;
-import android.support.v7.graphics.Palette;
 import android.util.Log;
 import android.util.Pair;
 
@@ -46,12 +49,6 @@
 
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-
-import static android.app.WallpaperManager.FLAG_SYSTEM;
-import static com.android.launcher3.Utilities.getDevicePrefs;
 
 public class WallpaperManagerCompatVL extends WallpaperManagerCompat {
 
@@ -260,27 +257,8 @@
             String value = VERSION_PREFIX + wallpaperId;
 
             if (bitmap != null) {
-                Palette palette = Palette.from(bitmap).generate();
-                bitmap.recycle();
-
-                StringBuilder builder = new StringBuilder(value);
-                List<Pair<Integer,Integer>> colorsToOccurrences = new ArrayList<>();
-                for (Palette.Swatch swatch : palette.getSwatches()) {
-                    colorsToOccurrences.add(new Pair(swatch.getRgb(), swatch.getPopulation()));
-                }
-
-                Collections.sort(colorsToOccurrences, new Comparator<Pair<Integer, Integer>>() {
-                    @Override
-                    public int compare(Pair<Integer, Integer> a, Pair<Integer, Integer> b) {
-                        return b.second - a.second;
-                    }
-                });
-
-                for (int i=0; i < Math.min(3, colorsToOccurrences.size()); i++) {
-                    builder.append(',').append(colorsToOccurrences.get(i).first);
-                }
-
-                value = builder.toString();
+                int color = Utilities.findDominantColorByHue(bitmap, MAX_WALLPAPER_EXTRACTION_AREA);
+                value += "," + color;
             }
 
             // Send the result
diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java
index 6a4cbcb..1924710 100644
--- a/src/com/android/launcher3/config/BaseFlags.java
+++ b/src/com/android/launcher3/config/BaseFlags.java
@@ -29,20 +29,14 @@
     BaseFlags() {}
 
     public static final boolean IS_DOGFOOD_BUILD = false;
+    public static final String AUTHORITY = "com.android.launcher3.settings".intern();
 
     // Custom flags go below this
     public static boolean LAUNCHER3_DISABLE_ICON_NORMALIZATION = false;
-    public static boolean LAUNCHER3_LEGACY_FOLDER_ICON = false;
-    public static boolean LAUNCHER3_DISABLE_PINCH_TO_OVERVIEW = false;
-    public static boolean LAUNCHER3_NEW_FOLDER_ANIMATION = true;
     // When enabled allows to use any point on the fast scrollbar to start dragging.
     public static final boolean LAUNCHER3_DIRECT_SCROLL = true;
-    // When enabled while all-apps open, the soft input will be set to adjust resize .
-    public static final boolean LAUNCHER3_UPDATE_SOFT_INPUT_MODE = false;
     // When enabled the promise icon is visible in all apps while installation an app.
     public static final boolean LAUNCHER3_PROMISE_APPS_IN_ALL_APPS = false;
-    // When enabled uses the AllAppsRadialGradientAndScrimDrawable for all apps
-    public static final boolean LAUNCHER3_GRADIENT_ALL_APPS = true;
     // When enabled allows use of physics based motions in the Launcher.
     public static final boolean LAUNCHER3_PHYSICS = true;
     // When enabled allows use of spring motions on the icons.
@@ -52,19 +46,19 @@
     public static final boolean QSB_ON_FIRST_SCREEN = true;
     // When enabled the all-apps icon is not added to the hotseat.
     public static final boolean NO_ALL_APPS_ICON = true;
-    // When enabled fling down gesture on the first workspace triggers search.
-    public static final boolean PULLDOWN_SEARCH = false;
-    // When enabled the status bar may show dark icons based on the top of the wallpaper.
-    public static final boolean LIGHT_STATUS_BAR = false;
     // When enabled, icons not supporting {@link AdaptiveIconDrawable} will be wrapped in {@link FixedScaleDrawable}.
     public static final boolean LEGACY_ICON_TREATMENT = true;
     // When enabled, adaptive icons would have shadows baked when being stored to icon cache.
     public static final boolean ADAPTIVE_ICON_SHADOW = true;
     // When enabled, app discovery will be enabled if service is implemented
     public static final boolean DISCOVERY_ENABLED = false;
-    // When enabled, the qsb will be moved to the hotseat.
-    public static final boolean QSB_IN_HOTSEAT = true;
+
+    // When true, custom widgets are loaded using CustomWidgetParser.
+    public static final boolean ENABLE_CUSTOM_WIDGETS = false;
 
     // Features to control Launcher3Go behavior
     public static final boolean GO_DISABLE_WIDGETS = false;
+
+    // When enabled shows a work profile tab in all apps
+    public static final boolean ALL_APPS_TABS_ENABLED = false;
 }
diff --git a/src/com/android/launcher3/discovery/AppDiscoveryAppInfo.java b/src/com/android/launcher3/discovery/AppDiscoveryAppInfo.java
index 06493b2..ed5cfeb 100644
--- a/src/com/android/launcher3/discovery/AppDiscoveryAppInfo.java
+++ b/src/com/android/launcher3/discovery/AppDiscoveryAppInfo.java
@@ -41,7 +41,6 @@
         this.intent = item.isInstantApp ? item.launchIntent : item.installIntent;
         this.title = item.title;
         this.iconBitmap = item.bitmap;
-        this.isDisabled = ShortcutInfo.DEFAULT;
         this.usingLowResIcon = false;
         this.isInstantApp = item.isInstantApp;
         this.isRecent = item.isRecent;
diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java
index c843e72..db199c1 100644
--- a/src/com/android/launcher3/dragndrop/AddItemActivity.java
+++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java
@@ -147,11 +147,12 @@
         // Start home and pass the draw request params
         PinItemDragListener listener = new PinItemDragListener(mRequest, bounds,
                 img.getBitmap().getWidth(), img.getWidth());
-        Intent homeIntent = new Intent(Intent.ACTION_MAIN)
-                .addCategory(Intent.CATEGORY_HOME)
-                .setPackage(getPackageName())
-                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-                .putExtra(PinItemDragListener.EXTRA_PIN_ITEM_DRAG_LISTENER, listener);
+
+        Intent homeIntent = listener.addToIntent(
+                new Intent(Intent.ACTION_MAIN)
+                        .addCategory(Intent.CATEGORY_HOME)
+                        .setPackage(getPackageName())
+                        .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
 
         if (!getResources().getBoolean(R.bool.allow_rotation) &&
                 !Utilities.isAllowRotationPrefEnabled(this) &&
diff --git a/src/com/android/launcher3/dragndrop/BaseItemDragListener.java b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
index d0f2629..4629dad 100644
--- a/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
+++ b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
@@ -16,24 +16,25 @@
 
 package com.android.launcher3.dragndrop;
 
+import static com.android.launcher3.LauncherState.NORMAL;
+
 import android.content.ClipDescription;
 import android.content.Intent;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.Handler;
 import android.os.Looper;
-import android.os.Parcel;
 import android.os.SystemClock;
 import android.util.Log;
 import android.view.DragEvent;
 import android.view.View;
 
-import com.android.launcher3.DeleteDropTarget;
+import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.DragSource;
-import com.android.launcher3.DropTarget;
+import com.android.launcher3.DropTarget.DragObject;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
-import com.android.launcher3.folder.Folder;
+import com.android.launcher3.states.InternalStateHandler;
 import com.android.launcher3.widget.PendingItemDragHelper;
 
 import java.util.UUID;
@@ -41,7 +42,7 @@
 /**
  * {@link DragSource} for handling drop from a different window.
  */
-public abstract class BaseItemDragListener implements
+public abstract class BaseItemDragListener extends InternalStateHandler implements
         View.OnDragListener, DragSource, DragOptions.PreDragCondition {
 
     private static final String TAG = "BaseItemDragListener";
@@ -69,25 +70,16 @@
         mId = UUID.randomUUID().toString();
     }
 
-    protected BaseItemDragListener(Parcel parcel) {
-        mPreviewRect = Rect.CREATOR.createFromParcel(parcel);
-        mPreviewBitmapWidth = parcel.readInt();
-        mPreviewViewWidth = parcel.readInt();
-        mId = parcel.readString();
-    }
-
-    protected void writeToParcel(Parcel parcel, int i) {
-        mPreviewRect.writeToParcel(parcel, i);
-        parcel.writeInt(mPreviewBitmapWidth);
-        parcel.writeInt(mPreviewViewWidth);
-        parcel.writeString(mId);
-    }
-
     public String getMimeType() {
         return MIME_TYPE_PREFIX + mId;
     }
 
-    public void setLauncher(Launcher launcher) {
+    @Override
+    public void init(Launcher launcher, boolean alreadyOnHome) {
+        AbstractFloatingView.closeAllOpenViews(launcher, alreadyOnHome);
+        launcher.getStateManager().goToState(NORMAL, alreadyOnHome /* animated */);
+        launcher.getDragLayer().setOnDragListener(this);
+
         mLauncher = launcher;
         mDragController = launcher.getDragController();
     }
@@ -141,7 +133,7 @@
     }
 
     @Override
-    public void onPreDragStart(DropTarget.DragObject dragObject) {
+    public void onPreDragStart(DragObject dragObject) {
         // The predrag starts when the workspace is not yet loaded. In some cases we set
         // the dragLayer alpha to 0 to have a nice fade-in animation. But that will prevent the
         // dragView from being visible. Instead just skip the fade-in animation here.
@@ -152,41 +144,14 @@
     }
 
     @Override
-    public void onPreDragEnd(DropTarget.DragObject dragObject, boolean dragStarted) {
+    public void onPreDragEnd(DragObject dragObject, boolean dragStarted) {
         if (dragStarted) {
             dragObject.dragView.setColor(0);
         }
     }
 
     @Override
-    public boolean supportsAppInfoDropTarget() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsDeleteDropTarget() {
-        return false;
-    }
-
-    @Override
-    public float getIntrinsicIconScaleFactor() {
-        return 1f;
-    }
-
-    @Override
-    public void onDropCompleted(View target, DropTarget.DragObject d, boolean isFlingToDelete,
-            boolean success) {
-        if (isFlingToDelete || !success || (target != mLauncher.getWorkspace() &&
-                !(target instanceof DeleteDropTarget) && !(target instanceof Folder))) {
-            // Exit spring loaded mode if we have not successfully dropped or have not handled the
-            // drop in Workspace
-            mLauncher.exitSpringLoadedDragModeDelayed(true,
-                    Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
-        }
-
-        if (!success) {
-            d.deferDragViewCleanupPostAnimation = false;
-        }
+    public void onDropCompleted(View target, DragObject d, boolean success) {
         postCleanup();
     }
 
@@ -211,4 +176,7 @@
             mLauncher.getDragLayer().setOnDragListener(null);
         }
     }
+
+    @Override
+    public void onLauncherResume() { }
 }
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index b852714..818cea7 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -16,6 +16,9 @@
 
 package com.android.launcher3.dragndrop;
 
+import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
+import static com.android.launcher3.LauncherState.NORMAL;
+
 import android.content.ComponentName;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
@@ -27,7 +30,6 @@
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.inputmethod.InputMethodManager;
 
 import com.android.launcher3.DragSource;
 import com.android.launcher3.DropTarget;
@@ -39,6 +41,7 @@
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.util.TouchController;
+import com.android.launcher3.util.UiThreadHelper;
 
 import java.util.ArrayList;
 
@@ -120,6 +123,9 @@
 
     /**
      * Starts a drag.
+     * When the drag is started, the UI automatically goes into spring loaded mode. On a successful
+     * drop, it is the responsibility of the {@link DropTarget} to exit out of the spring loaded
+     * mode. If the drop was cancelled for some reason, the UI will automatically exit out of this mode.
      *
      * @param b The bitmap to display as the drag image.  It will be re-scaled to the
      *          enlarged size.
@@ -138,8 +144,7 @@
         }
 
         // Hide soft keyboard, if visible
-        mLauncher.getSystemService(InputMethodManager.class)
-                .hideSoftInputFromWindow(mWindowToken, 0);
+        UiThreadHelper.hideKeyboardAsync(mLauncher, mWindowToken);
 
         mOptions = options;
         if (mOptions.systemDndStartPoint != null) {
@@ -249,12 +254,23 @@
             mDragObject.cancelled = true;
             mDragObject.dragComplete = true;
             if (!mIsInPreDrag) {
-                mDragObject.dragSource.onDropCompleted(null, mDragObject, false, false);
+                dispatchDropComplete(null, false);
             }
         }
         endDrag();
     }
 
+    private void dispatchDropComplete(View dropTarget, boolean accepted) {
+        if (!accepted) {
+            // If it was not accepted, cleanup the state. If it was accepted, it is the
+            // responsibility of the drop target to cleanup the state.
+            mLauncher.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
+            mDragObject.deferDragViewCleanupPostAnimation = false;
+        }
+
+        mDragObject.dragSource.onDropCompleted(dropTarget, mDragObject, accepted);
+    }
+
     public void onAppsRemoved(ItemInfoMatcher matcher) {
         // Cancel the current drag if we are removing an app that we are dragging
         if (mDragObject != null) {
@@ -576,7 +592,7 @@
                 if (flingAnimation != null) {
                     flingAnimation.run();
                 } else if (!mIsInPreDrag) {
-                    dropTarget.onDrop(mDragObject);
+                    dropTarget.onDrop(mDragObject, mOptions);
                 }
                 accepted = true;
             }
@@ -584,35 +600,37 @@
         final View dropTargetAsView = dropTarget instanceof View ? (View) dropTarget : null;
         if (!mIsInPreDrag) {
             mLauncher.getUserEventDispatcher().logDragNDrop(mDragObject, dropTargetAsView);
-            mDragObject.dragSource.onDropCompleted(
-                    dropTargetAsView, mDragObject, flingAnimation != null, accepted);
+            dispatchDropComplete(dropTargetAsView, accepted);
         }
     }
 
     private DropTarget findDropTarget(int x, int y, int[] dropCoordinates) {
-        final Rect r = mRectTemp;
+        mDragObject.x = x;
+        mDragObject.y = y;
 
+        final Rect r = mRectTemp;
         final ArrayList<DropTarget> dropTargets = mDropTargets;
         final int count = dropTargets.size();
-        for (int i=count-1; i>=0; i--) {
+        for (int i = count - 1; i >= 0; i--) {
             DropTarget target = dropTargets.get(i);
             if (!target.isDropEnabled())
                 continue;
 
             target.getHitRectRelativeToDragLayer(r);
-
-            mDragObject.x = x;
-            mDragObject.y = y;
             if (r.contains(x, y)) {
-
                 dropCoordinates[0] = x;
                 dropCoordinates[1] = y;
                 mLauncher.getDragLayer().mapCoordInSelfToDescendant((View) target, dropCoordinates);
-
                 return target;
             }
         }
-        return null;
+        // Pass all unhandled drag to workspace. Workspace finds the correct
+        // cell layout to drop to in the existing drag/drop logic.
+        dropCoordinates[0] = x;
+        dropCoordinates[1] = y;
+        mLauncher.getDragLayer().mapCoordInSelfToDescendant(mLauncher.getWorkspace(),
+                dropCoordinates);
+        return mLauncher.getWorkspace();
     }
 
     public void setWindowToken(IBinder token) {
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index fde7995..9f9822c 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -24,46 +24,36 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Canvas;
-import android.graphics.Color;
 import android.graphics.Rect;
-import android.graphics.Region;
-import android.support.v4.graphics.ColorUtils;
+import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.view.KeyEvent;
-import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
-import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
 import android.widget.FrameLayout;
 import android.widget.TextView;
 
 import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.AppWidgetResizeFrame;
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DropTargetBar;
-import com.android.launcher3.ExtendedEditText;
 import com.android.launcher3.InsettableFrameLayout;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppWidgetHostView;
-import com.android.launcher3.PinchToOverviewListener;
 import com.android.launcher3.R;
 import com.android.launcher3.ShortcutAndWidgetContainer;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.allapps.AllAppsTransitionController;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.dynamicui.WallpaperColorInfo;
+import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.keyboard.ViewGroupFocusHelper;
-import com.android.launcher3.logging.LoggerUtils;
+import com.android.launcher3.uioverrides.UiFactory;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.util.TouchController;
-import com.android.launcher3.widget.WidgetsBottomSheet;
 
 import java.util.ArrayList;
 
@@ -81,40 +71,29 @@
 
     private Launcher mLauncher;
 
-    // Variables relating to resizing widgets
-    private final boolean mIsRtl;
-    private AppWidgetResizeFrame mCurrentResizeFrame;
-
     // Variables relating to animation of views after drop
     private ValueAnimator mDropAnim = null;
-    private final TimeInterpolator mCubicEaseOutInterpolator = new DecelerateInterpolator(1.5f);
+    private final TimeInterpolator mCubicEaseOutInterpolator = Interpolators.DEACCEL_1_5;
     @Thunk DragView mDropView = null;
     @Thunk int mAnchorViewInitialScrollX = 0;
     @Thunk View mAnchorView = null;
 
     private boolean mHoverPointClosesFolder = false;
     private final Rect mHitRect = new Rect();
-    private final Rect mHighlightRect = new Rect();
 
     private TouchCompleteListener mTouchCompleteListener;
 
     private int mTopViewIndex;
     private int mChildCountOnLastUpdate = -1;
 
-    // Darkening scrim
-    private float mBackgroundAlpha = 0;
-
     // Related to adjacent page hints
-    private final Rect mScrollChildPosition = new Rect();
     private final ViewGroupFocusHelper mFocusIndicatorHelper;
-    private final WallpaperColorInfo mWallpaperColorInfo;
-
-    // Related to pinch-to-go-to-overview gesture.
-    private PinchToOverviewListener mPinchListener = null;
+    private final PageCutOutScrimDrawable mPageCutOutScrim;
 
     // Handles all apps pull up interaction
     private AllAppsTransitionController mAllAppsController;
 
+    protected TouchController[] mControllers;
     private TouchController mActiveController;
     /**
      * Used to create a new DragLayer from XML.
@@ -129,9 +108,9 @@
         setMotionEventSplittingEnabled(false);
         setChildrenDrawingOrderEnabled(true);
 
-        mIsRtl = Utilities.isRtl(getResources());
         mFocusIndicatorHelper = new ViewGroupFocusHelper(this);
-        mWallpaperColorInfo = WallpaperColorInfo.getInstance(getContext());
+        mPageCutOutScrim = new PageCutOutScrimDrawable(this);
+        mPageCutOutScrim.setCallback(this);
     }
 
     public void setup(Launcher launcher, DragController dragController,
@@ -139,10 +118,7 @@
         mLauncher = launcher;
         mDragController = dragController;
         mAllAppsController = allAppsTransitionController;
-
-        boolean isAccessibilityEnabled = ((AccessibilityManager) mLauncher.getSystemService(
-                Context.ACCESSIBILITY_SERVICE)).isEnabled();
-        onAccessibilityStateChanged(isAccessibilityEnabled);
+        mControllers = UiFactory.createTouchControllers(mLauncher);
     }
 
     public ViewGroupFocusHelper getFocusIndicatorHelper() {
@@ -154,13 +130,9 @@
         return mDragController.dispatchKeyEvent(event) || super.dispatchKeyEvent(event);
     }
 
-    public void onAccessibilityStateChanged(boolean isAccessibilityEnabled) {
-        mPinchListener = FeatureFlags.LAUNCHER3_DISABLE_PINCH_TO_OVERVIEW || isAccessibilityEnabled
-                ? null : new PinchToOverviewListener(mLauncher);
-    }
-
-    public boolean isEventOverPageIndicator(MotionEvent ev) {
-        return isEventOverView(mLauncher.getWorkspace().getPageIndicator(), ev);
+    @Override
+    protected boolean verifyDrawable(Drawable who) {
+        return super.verifyDrawable(who) || who == mPageCutOutScrim;
     }
 
     public boolean isEventOverHotseat(MotionEvent ev) {
@@ -180,36 +152,6 @@
         return mHitRect.contains((int) ev.getX(), (int) ev.getY());
     }
 
-    private boolean handleTouchDown(MotionEvent ev, boolean intercept) {
-        AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mLauncher);
-        if (topView != null && intercept) {
-            ExtendedEditText textView = topView.getActiveTextView();
-            if (textView != null) {
-                if (!isEventOverView(textView, ev)) {
-                    textView.dispatchBackKey();
-                    return true;
-                }
-            } else if (!isEventOverView(topView, ev)) {
-                if (isInAccessibleDrag()) {
-                    // Do not close the container if in drag and drop.
-                    if (!isEventOverDropTargetBar(ev)) {
-                        return true;
-                    }
-                } else {
-                    mLauncher.getUserEventDispatcher().logActionTapOutside(
-                            LoggerUtils.newContainerTarget(topView.getLogContainerType()));
-                    topView.close(true);
-
-                    // We let touches on the original icon go through so that users can launch
-                    // the app with one tap if they don't find a shortcut they want.
-                    View extendedTouch = topView.getExtendedTouchView();
-                    return extendedTouch == null || !isEventOverView(extendedTouch, ev);
-                }
-            }
-        }
-        return false;
-    }
-
     @Override
     public boolean onInterceptTouchEvent(MotionEvent ev) {
         int action = ev.getAction();
@@ -219,9 +161,6 @@
             // dray layer even if mAllAppsController is NOT the active controller.
             // TODO: handle other input other than touch
             mAllAppsController.cancelDiscoveryAnimation();
-            if (handleTouchDown(ev, true)) {
-                return true;
-            }
         } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
             if (mTouchCompleteListener != null) {
                 mTouchCompleteListener.onTouchComplete();
@@ -230,12 +169,10 @@
         }
         mActiveController = null;
 
-        if (mCurrentResizeFrame != null
-                && mCurrentResizeFrame.onControllerInterceptTouchEvent(ev)) {
-            mActiveController = mCurrentResizeFrame;
+        AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mLauncher);
+        if (topView != null && topView.onControllerInterceptTouchEvent(ev)) {
+            mActiveController = topView;
             return true;
-        } else {
-            clearResizeFrame();
         }
 
         if (mDragController.onControllerInterceptTouchEvent(ev)) {
@@ -243,21 +180,11 @@
             return true;
         }
 
-        if (mAllAppsController.onControllerInterceptTouchEvent(ev)) {
-            mActiveController = mAllAppsController;
-            return true;
-        }
-
-        WidgetsBottomSheet widgetsBottomSheet = WidgetsBottomSheet.getOpen(mLauncher);
-        if (widgetsBottomSheet != null && widgetsBottomSheet.onControllerInterceptTouchEvent(ev)) {
-            mActiveController = widgetsBottomSheet;
-            return true;
-        }
-
-        if (mPinchListener != null && mPinchListener.onControllerInterceptTouchEvent(ev)) {
-            // Stop listening for scrolling etc. (onTouchEvent() handles the rest of the pinch.)
-            mActiveController = mPinchListener;
-            return true;
+        for (TouchController controller : mControllers) {
+            if (controller.onControllerInterceptTouchEvent(ev)) {
+                mActiveController = controller;
+                return true;
+            }
         }
         return false;
     }
@@ -357,12 +284,7 @@
     @Override
     public boolean onTouchEvent(MotionEvent ev) {
         int action = ev.getAction();
-
-        if (action == MotionEvent.ACTION_DOWN) {
-            if (handleTouchDown(ev, false)) {
-                return true;
-            }
-        } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
+        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
             if (mTouchCompleteListener != null) {
                 mTouchCompleteListener.onTouchComplete();
             }
@@ -542,25 +464,6 @@
         }
     }
 
-    public void clearResizeFrame() {
-        if (mCurrentResizeFrame != null) {
-            removeView(mCurrentResizeFrame);
-            mCurrentResizeFrame = null;
-        }
-    }
-
-    public void addResizeFrame(LauncherAppWidgetHostView widget, CellLayout cellLayout) {
-        clearResizeFrame();
-
-        mCurrentResizeFrame = (AppWidgetResizeFrame) LayoutInflater.from(mLauncher)
-                .inflate(R.layout.app_widget_resize_frame, this, false);
-        mCurrentResizeFrame.setupForWidget(widget, cellLayout, this);
-        ((LayoutParams) mCurrentResizeFrame.getLayoutParams()).customPosition = true;
-
-        addView(mCurrentResizeFrame);
-        mCurrentResizeFrame.snapToWidget(false);
-    }
-
     public void animateViewIntoPosition(DragView dragView, final int[] pos, float alpha,
             float scaleX, float scaleY, int animationEndStyle, Runnable onFinishRunnable,
             int duration) {
@@ -573,13 +476,12 @@
                 onFinishRunnable, animationEndStyle, duration, null);
     }
 
-    public void animateViewIntoPosition(DragView dragView, final View child,
-            final Runnable onFinishAnimationRunnable, View anchorView) {
-        animateViewIntoPosition(dragView, child, -1, onFinishAnimationRunnable, anchorView);
+    public void animateViewIntoPosition(DragView dragView, final View child, View anchorView) {
+        animateViewIntoPosition(dragView, child, -1, anchorView);
     }
 
     public void animateViewIntoPosition(DragView dragView, final View child, int duration,
-            final Runnable onFinishAnimationRunnable, View anchorView) {
+            View anchorView) {
         ShortcutAndWidgetContainer parentChildren = (ShortcutAndWidgetContainer) child.getParent();
         CellLayout.LayoutParams lp =  (CellLayout.LayoutParams) child.getLayoutParams();
         parentChildren.measureChild(child);
@@ -636,9 +538,6 @@
         Runnable onCompleteRunnable = new Runnable() {
             public void run() {
                 child.setVisibility(VISIBLE);
-                if (onFinishAnimationRunnable != null) {
-                    onFinishAnimationRunnable.run();
-                }
             }
         };
         animateViewIntoPosition(dragView, fromX, fromY, toX, toY, 1, 1, 1, toScale, toScale,
@@ -804,13 +703,14 @@
     }
 
     @Override
-    public void onChildViewAdded(View parent, View child) {
-        super.onChildViewAdded(parent, child);
+    public void onViewAdded(View child) {
+        super.onViewAdded(child);
         updateChildIndices();
     }
 
     @Override
-    public void onChildViewRemoved(View parent, View child) {
+    public void onViewRemoved(View child) {
+        super.onViewRemoved(child);
         updateChildIndices();
     }
 
@@ -858,48 +758,23 @@
     }
 
     public void invalidateScrim() {
-        if (mBackgroundAlpha > 0.0f) {
+        if (mPageCutOutScrim.getAlpha() > 0) {
             invalidate();
         }
     }
 
+    public Drawable getScrim() {
+        return mPageCutOutScrim;
+    }
+
     @Override
     protected void dispatchDraw(Canvas canvas) {
         // Draw the background below children.
-        if (mBackgroundAlpha > 0.0f) {
-            // Update the scroll position first to ensure scrim cutout is in the right place.
-            mLauncher.getWorkspace().computeScrollWithoutInvalidation();
-
-            int alpha = (int) (mBackgroundAlpha * 255);
-            CellLayout currCellLayout = mLauncher.getWorkspace().getCurrentDragOverlappingLayout();
-            canvas.save();
-            if (currCellLayout != null && currCellLayout != mLauncher.getHotseat().getLayout()) {
-                // Cut a hole in the darkening scrim on the page that should be highlighted, if any.
-                getDescendantRectRelativeToSelf(currCellLayout, mHighlightRect);
-                canvas.clipRect(mHighlightRect, Region.Op.DIFFERENCE);
-            }
-            // for super light wallpaper it needs to be darken for contrast to workspace
-            // for dark wallpapers the text is white so darkening works as well
-            int color = ColorUtils.compositeColors(0x66000000, mWallpaperColorInfo.getMainColor());
-            canvas.drawColor(ColorUtils.setAlphaComponent(color, alpha));
-            canvas.restore();
-        }
-
+        mPageCutOutScrim.draw(canvas);
         mFocusIndicatorHelper.draw(canvas);
         super.dispatchDraw(canvas);
     }
 
-    public void setBackgroundAlpha(float alpha) {
-        if (alpha != mBackgroundAlpha) {
-            mBackgroundAlpha = alpha;
-            invalidate();
-        }
-    }
-
-    public float getBackgroundAlpha() {
-        return mBackgroundAlpha;
-    }
-
     @Override
     protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
         View topView = AbstractFloatingView.getTopOpenView(mLauncher);
diff --git a/src/com/android/launcher3/dragndrop/DragOptions.java b/src/com/android/launcher3/dragndrop/DragOptions.java
index 9433aad..f108f8b 100644
--- a/src/com/android/launcher3/dragndrop/DragOptions.java
+++ b/src/com/android/launcher3/dragndrop/DragOptions.java
@@ -34,6 +34,9 @@
     /** Determines when a pre-drag should transition to a drag. By default, this is immediate. */
     public PreDragCondition preDragCondition = null;
 
+    /** Scale of the icons over the workspace icon size. */
+    public float intrinsicIconScaleFactor = 1f;
+
     /**
      * Specifies a condition that must be met before DragListener#onDragStart() is called.
      * By default, there is no condition and onDragStart() is called immediately following
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index 33d4fa6..7c89df3 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -43,7 +43,6 @@
 import android.support.animation.SpringAnimation;
 import android.support.animation.SpringForce;
 import android.view.View;
-import android.view.animation.DecelerateInterpolator;
 
 import com.android.launcher3.FastBitmapDrawable;
 import com.android.launcher3.ItemInfo;
@@ -54,6 +53,7 @@
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.ShortcutConfigActivityInfo;
 import com.android.launcher3.config.FeatureFlags;
@@ -169,7 +169,7 @@
             }
         });
 
-        mBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight());
+        mBitmap = bitmap;
         setDragRegion(new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()));
 
         // The point in our scaled bitmap that the touch events are located
@@ -427,6 +427,10 @@
         return mDragRegion;
     }
 
+    public Bitmap getPreviewBitmap() {
+        return mBitmap;
+    }
+
     @Override
     protected void onDraw(Canvas canvas) {
         mHasDrawn = true;
@@ -468,7 +472,7 @@
     public void crossFade(int duration) {
         ValueAnimator va = LauncherAnimUtils.ofFloat(0f, 1f);
         va.setDuration(duration);
-        va.setInterpolator(new DecelerateInterpolator(1.5f));
+        va.setInterpolator(Interpolators.DEACCEL_1_5);
         va.addUpdateListener(new AnimatorUpdateListener() {
             @Override
             public void onAnimationUpdate(ValueAnimator animation) {
diff --git a/src/com/android/launcher3/dragndrop/PageCutOutScrimDrawable.java b/src/com/android/launcher3/dragndrop/PageCutOutScrimDrawable.java
new file mode 100644
index 0000000..367124b
--- /dev/null
+++ b/src/com/android/launcher3/dragndrop/PageCutOutScrimDrawable.java
@@ -0,0 +1,90 @@
+/*
+ * 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.dragndrop;
+
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.graphics.drawable.Drawable;
+import android.support.v4.graphics.ColorUtils;
+
+import com.android.launcher3.CellLayout;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.dynamicui.WallpaperColorInfo;
+
+/**
+ * Scrim drawable which draws a hole for the current drop target page.
+ */
+public class PageCutOutScrimDrawable extends Drawable {
+
+    private final Rect mHighlightRect = new Rect();
+    private final DragLayer mDragLayer;
+    private final Launcher mLauncher;
+    private final WallpaperColorInfo mWallpaperColorInfo;
+
+    private int mAlpha = 0;
+
+    public PageCutOutScrimDrawable(DragLayer dragLayer) {
+        mDragLayer = dragLayer;
+        mLauncher = Launcher.getLauncher(mDragLayer.getContext());
+        mWallpaperColorInfo = WallpaperColorInfo.getInstance(mLauncher);
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        // Draw the background below children.
+        if (mAlpha > 0) {
+            // Update the scroll position first to ensure scrim cutout is in the right place.
+            mLauncher.getWorkspace().computeScrollWithoutInvalidation();
+            CellLayout currCellLayout = mLauncher.getWorkspace().getCurrentDragOverlappingLayout();
+            canvas.save();
+            if (currCellLayout != null && currCellLayout != mLauncher.getHotseat().getLayout()) {
+                // Cut a hole in the darkening scrim on the page that should be highlighted, if any.
+                mDragLayer.getDescendantRectRelativeToSelf(currCellLayout, mHighlightRect);
+                canvas.clipRect(mHighlightRect, Region.Op.DIFFERENCE);
+            }
+            // for super light wallpaper it needs to be darken for contrast to workspace
+            // for dark wallpapers the text is white so darkening works as well
+            int color = ColorUtils.compositeColors(0x66000000, mWallpaperColorInfo.getMainColor());
+            canvas.drawColor(ColorUtils.setAlphaComponent(color, mAlpha));
+            canvas.restore();
+        }
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+        if (mAlpha != alpha) {
+            mAlpha = alpha;
+            invalidateSelf();
+        }
+    }
+
+    @Override
+    public int getAlpha() {
+        return mAlpha;
+    }
+
+    @Override
+    public void setColorFilter(ColorFilter colorFilter) { }
+
+    @Override
+    public int getOpacity() {
+        return PixelFormat.TRANSLUCENT;
+    }
+}
diff --git a/src/com/android/launcher3/dragndrop/PinItemDragListener.java b/src/com/android/launcher3/dragndrop/PinItemDragListener.java
index b9d97ac..924bb4c 100644
--- a/src/com/android/launcher3/dragndrop/PinItemDragListener.java
+++ b/src/com/android/launcher3/dragndrop/PinItemDragListener.java
@@ -18,23 +18,18 @@
 
 import android.annotation.TargetApi;
 import android.appwidget.AppWidgetManager;
-import android.content.Intent;
 import android.content.pm.LauncherApps.PinItemRequest;
 import android.graphics.Rect;
 import android.os.Build;
 import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
 import android.view.DragEvent;
 import android.view.View;
 import android.widget.RemoteViews;
 
 import com.android.launcher3.DragSource;
 import com.android.launcher3.ItemInfo;
-import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.PendingAddItemInfo;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.widget.PendingAddShortcutInfo;
 import com.android.launcher3.widget.PendingAddWidgetInfo;
@@ -46,9 +41,7 @@
  * in the source window and is passed on to the Launcher activity as an Intent extra.
  */
 @TargetApi(Build.VERSION_CODES.O)
-public class PinItemDragListener extends BaseItemDragListener implements Parcelable {
-
-    public static final String EXTRA_PIN_ITEM_DRAG_LISTENER = "pin_item_drag_listener";
+public class PinItemDragListener extends BaseItemDragListener {
 
     private final PinItemRequest mRequest;
 
@@ -58,22 +51,6 @@
         mRequest = request;
     }
 
-    private PinItemDragListener(Parcel parcel) {
-        super(parcel);
-        mRequest = PinItemRequest.CREATOR.createFromParcel(parcel);
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(Parcel parcel, int i) {
-        super.writeToParcel(parcel, i);
-        mRequest.writeToParcel(parcel, i);
-    }
-
     @Override
     protected boolean onDragStart(DragEvent event) {
         if (!mRequest.isValid()) {
@@ -126,33 +103,4 @@
         }
         return null;
     }
-
-    public static boolean handleDragRequest(Launcher launcher, Intent intent) {
-        if (!Utilities.ATLEAST_OREO) {
-            return false;
-        }
-        if (intent == null || !Intent.ACTION_MAIN.equals(intent.getAction())) {
-            return false;
-        }
-        Parcelable dragExtra = intent.getParcelableExtra(EXTRA_PIN_ITEM_DRAG_LISTENER);
-        if (dragExtra instanceof PinItemDragListener) {
-            PinItemDragListener dragListener = (PinItemDragListener) dragExtra;
-            dragListener.setLauncher(launcher);
-
-            launcher.getDragLayer().setOnDragListener(dragListener);
-            return true;
-        }
-        return false;
-    }
-
-    public static final Parcelable.Creator<PinItemDragListener> CREATOR =
-            new Parcelable.Creator<PinItemDragListener>() {
-                public PinItemDragListener createFromParcel(Parcel source) {
-                    return new PinItemDragListener(source);
-                }
-
-                public PinItemDragListener[] newArray(int size) {
-                    return new PinItemDragListener[size];
-                }
-            };
 }
diff --git a/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
index a70a9bb..52167bb 100644
--- a/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
+++ b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
@@ -29,7 +29,7 @@
 
 import com.android.launcher3.FastBitmapDrawable;
 import com.android.launcher3.IconCache;
-import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
@@ -83,8 +83,8 @@
     public com.android.launcher3.ShortcutInfo createShortcutInfo() {
         // Total duration for the drop animation to complete.
         long duration = mContext.getResources().getInteger(R.integer.config_dropAnimMaxDuration) +
-                Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT +
-                mContext.getResources().getInteger(R.integer.config_overlayTransitionTime) / 2;
+                LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY +
+                LauncherAnimUtils.SPRING_LOADED_TRANSITION_MS;
         // Delay the actual accept() call until the drop animation is complete.
         return LauncherAppsCompatVO.createShortcutInfoFromPinItemRequest(
                 mContext, mRequest, duration);
diff --git a/src/com/android/launcher3/dynamicui/ColorExtractionService.java b/src/com/android/launcher3/dynamicui/ColorExtractionService.java
deleted file mode 100644
index b9dd3b5..0000000
--- a/src/com/android/launcher3/dynamicui/ColorExtractionService.java
+++ /dev/null
@@ -1,204 +0,0 @@
-/*
- * Copyright (C) 2016 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.dynamicui;
-
-import android.annotation.TargetApi;
-import android.app.WallpaperManager;
-import android.app.job.JobParameters;
-import android.app.job.JobService;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.BitmapRegionDecoder;
-import android.graphics.Rect;
-import android.graphics.drawable.BitmapDrawable;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.ParcelFileDescriptor;
-import android.support.v7.graphics.Palette;
-import android.util.Log;
-
-import com.android.launcher3.LauncherProvider;
-import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
-
-import java.io.IOException;
-
-/**
- * Extracts colors from the wallpaper, and saves results to {@link LauncherProvider}.
- */
-public class ColorExtractionService extends JobService {
-
-    private static final String TAG = "ColorExtractionService";
-    private static final boolean DEBUG = false;
-
-    /** The fraction of the wallpaper to extract colors for use on the hotseat. */
-    private static final float HOTSEAT_FRACTION = 1f / 4;
-
-    private HandlerThread mWorkerThread;
-    private Handler mWorkerHandler;
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-        mWorkerThread = new HandlerThread("ColorExtractionService");
-        mWorkerThread.start();
-        mWorkerHandler = new Handler(mWorkerThread.getLooper());
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-        mWorkerThread.quit();
-    }
-
-    @Override
-    public boolean onStartJob(final JobParameters jobParameters) {
-        if (DEBUG) Log.d(TAG, "onStartJob");
-        mWorkerHandler.post(new Runnable() {
-            @Override
-            public void run() {
-                WallpaperManager wallpaperManager = WallpaperManager.getInstance(
-                        ColorExtractionService.this);
-                int wallpaperId = ExtractionUtils.getWallpaperId(wallpaperManager);
-
-                ExtractedColors extractedColors = new ExtractedColors();
-                if (wallpaperManager.getWallpaperInfo() != null) {
-                    // We can't extract colors from live wallpapers; always use the default color.
-                    extractedColors.updateHotseatPalette(null);
-
-                    if (FeatureFlags.QSB_IN_HOTSEAT || FeatureFlags.LAUNCHER3_GRADIENT_ALL_APPS) {
-                        extractedColors.updateWallpaperThemePalette(null);
-                    }
-                } else {
-                    // We extract colors for the hotseat and status bar separately,
-                    // since they only consider part of the wallpaper.
-                    extractedColors.updateHotseatPalette(getHotseatPalette());
-
-                    if (FeatureFlags.LIGHT_STATUS_BAR) {
-                        extractedColors.updateStatusBarPalette(getStatusBarPalette());
-                    }
-
-                    if (FeatureFlags.QSB_IN_HOTSEAT || FeatureFlags.LAUNCHER3_GRADIENT_ALL_APPS) {
-                        extractedColors.updateWallpaperThemePalette(getWallpaperPalette());
-                    }
-                }
-
-                // Save the extracted colors and wallpaper id to LauncherProvider.
-                String colorsString = extractedColors.encodeAsString();
-                Bundle extras = new Bundle();
-                extras.putInt(LauncherSettings.Settings.EXTRA_WALLPAPER_ID, wallpaperId);
-                extras.putString(LauncherSettings.Settings.EXTRA_EXTRACTED_COLORS, colorsString);
-                getContentResolver().call(
-                        LauncherSettings.Settings.CONTENT_URI,
-                        LauncherSettings.Settings.METHOD_SET_EXTRACTED_COLORS_AND_WALLPAPER_ID,
-                        null, extras);
-                jobFinished(jobParameters, false /* needsReschedule */);
-                if (DEBUG) Log.d(TAG, "job finished!");
-            }
-        });
-        return true;
-    }
-
-    @Override
-    public boolean onStopJob(JobParameters jobParameters) {
-        if (DEBUG) Log.d(TAG, "onStopJob");
-        mWorkerHandler.removeCallbacksAndMessages(null);
-        return true;
-    }
-
-    @TargetApi(Build.VERSION_CODES.N)
-    private Palette getHotseatPalette() {
-        WallpaperManager wallpaperManager = WallpaperManager.getInstance(this);
-        if (Utilities.ATLEAST_NOUGAT) {
-            try (ParcelFileDescriptor fd = wallpaperManager
-                    .getWallpaperFile(WallpaperManager.FLAG_SYSTEM)) {
-                BitmapRegionDecoder decoder = BitmapRegionDecoder
-                        .newInstance(fd.getFileDescriptor(), false);
-                int height = decoder.getHeight();
-                Rect decodeRegion = new Rect(0, (int) (height * (1f - HOTSEAT_FRACTION)),
-                        decoder.getWidth(), height);
-                Bitmap bitmap = decoder.decodeRegion(decodeRegion, null);
-                decoder.recycle();
-                if (bitmap != null) {
-                    return Palette.from(bitmap).clearFilters().generate();
-                }
-            } catch (IOException | NullPointerException e) {
-                Log.e(TAG, "Fetching partial bitmap failed, trying old method", e);
-            }
-        }
-
-        Bitmap wallpaper = ((BitmapDrawable) wallpaperManager.getDrawable()).getBitmap();
-        return Palette.from(wallpaper)
-                .setRegion(0, (int) (wallpaper.getHeight() * (1f - HOTSEAT_FRACTION)),
-                        wallpaper.getWidth(), wallpaper.getHeight())
-                .clearFilters()
-                .generate();
-    }
-
-    @TargetApi(Build.VERSION_CODES.N)
-    private Palette getStatusBarPalette() {
-        WallpaperManager wallpaperManager = WallpaperManager.getInstance(this);
-        int statusBarHeight = getResources()
-                .getDimensionPixelSize(R.dimen.status_bar_height);
-
-        if (Utilities.ATLEAST_NOUGAT) {
-            try (ParcelFileDescriptor fd = wallpaperManager
-                    .getWallpaperFile(WallpaperManager.FLAG_SYSTEM)) {
-                BitmapRegionDecoder decoder = BitmapRegionDecoder
-                        .newInstance(fd.getFileDescriptor(), false);
-                Rect decodeRegion = new Rect(0, 0,
-                        decoder.getWidth(), statusBarHeight);
-                Bitmap bitmap = decoder.decodeRegion(decodeRegion, null);
-                decoder.recycle();
-                if (bitmap != null) {
-                    return Palette.from(bitmap).clearFilters().generate();
-                }
-            } catch (IOException | NullPointerException e) {
-                Log.e(TAG, "Fetching partial bitmap failed, trying old method", e);
-            }
-        }
-
-        Bitmap wallpaper = ((BitmapDrawable) wallpaperManager.getDrawable()).getBitmap();
-        return Palette.from(wallpaper)
-                .setRegion(0, 0, wallpaper.getWidth(), statusBarHeight)
-                .clearFilters()
-                .generate();
-    }
-
-    @TargetApi(Build.VERSION_CODES.N)
-    private Palette getWallpaperPalette() {
-        WallpaperManager wallpaperManager = WallpaperManager.getInstance(this);
-        if (Utilities.ATLEAST_NOUGAT) {
-            try (ParcelFileDescriptor fd = wallpaperManager
-                    .getWallpaperFile(WallpaperManager.FLAG_SYSTEM)) {
-                Bitmap bitmap = BitmapFactory.decodeFileDescriptor(fd.getFileDescriptor());
-                if (bitmap != null) {
-                    return Palette.from(bitmap).clearFilters().generate();
-                }
-            } catch (IOException | NullPointerException e) {
-                Log.e(TAG, "Fetching partial bitmap failed, trying old method", e);
-            }
-        }
-
-        Bitmap wallpaper = ((BitmapDrawable) wallpaperManager.getDrawable()).getBitmap();
-        return Palette.from(wallpaper).clearFilters().generate();
-    }
-}
diff --git a/src/com/android/launcher3/dynamicui/ExtractedColors.java b/src/com/android/launcher3/dynamicui/ExtractedColors.java
deleted file mode 100644
index 2d8bb86..0000000
--- a/src/com/android/launcher3/dynamicui/ExtractedColors.java
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
- * Copyright (C) 2016 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.dynamicui;
-
-import android.app.WallpaperManager;
-import android.content.Context;
-import android.graphics.Color;
-import android.support.annotation.Nullable;
-import android.support.v4.graphics.ColorUtils;
-import android.support.v7.graphics.Palette;
-import android.util.Log;
-
-import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-
-/**
- * Saves and loads colors extracted from the wallpaper, as well as the associated wallpaper id.
- */
-public class ExtractedColors {
-    private static final String TAG = "ExtractedColors";
-
-    public static final int DEFAULT_LIGHT = Color.WHITE;
-    public static final int DEFAULT_DARK = Color.BLACK;
-
-    // These color profile indices should NOT be changed, since they are used when saving and
-    // loading extracted colors. New colors should always be added at the end.
-    public static final int VERSION_INDEX = 0;
-    public static final int HOTSEAT_INDEX = 1;
-    public static final int STATUS_BAR_INDEX = 2;
-    public static final int WALLPAPER_VIBRANT_INDEX = 3;
-    public static final int ALLAPPS_GRADIENT_MAIN_INDEX = 4;
-    public static final int ALLAPPS_GRADIENT_SECONDARY_INDEX = 5;
-
-    private static final int VERSION;
-    private static final int[] DEFAULT_VALUES;
-
-    static {
-        if (FeatureFlags.LAUNCHER3_GRADIENT_ALL_APPS) {
-            VERSION = 3;
-            DEFAULT_VALUES = new int[] {
-                    VERSION,            // VERSION_INDEX
-                    0x40FFFFFF,         // HOTSEAT_INDEX: White with 25% alpha
-                    DEFAULT_DARK,       // STATUS_BAR_INDEX
-                    0xFFCCCCCC,         // WALLPAPER_VIBRANT_INDEX
-                    0xFF000000,         // ALLAPPS_GRADIENT_MAIN_INDEX
-                    0xFF000000          // ALLAPPS_GRADIENT_SECONDARY_INDEX
-            };
-        } else if (FeatureFlags.QSB_IN_HOTSEAT) {
-            VERSION = 2;
-            DEFAULT_VALUES = new int[] {
-                    VERSION,            // VERSION_INDEX
-                    0x40FFFFFF,         // HOTSEAT_INDEX: White with 25% alpha
-                    DEFAULT_DARK,       // STATUS_BAR_INDEX
-                    0xFFCCCCCC,         // WALLPAPER_VIBRANT_INDEX
-            };
-        } else {
-            VERSION = 1;
-            DEFAULT_VALUES = new int[] {
-                    VERSION,            // VERSION_INDEX
-                    0x40FFFFFF,         // HOTSEAT_INDEX: White with 25% alpha
-                    DEFAULT_DARK,       // STATUS_BAR_INDEX
-            };
-        }
-    }
-
-    private static final String COLOR_SEPARATOR = ",";
-
-    private final ArrayList<OnChangeListener> mListeners = new ArrayList<>();
-    private final int[] mColors;
-
-    public ExtractedColors() {
-        // The first entry is reserved for the version number.
-        mColors = Arrays.copyOf(DEFAULT_VALUES, DEFAULT_VALUES.length);
-    }
-
-    public void setColorAtIndex(int index, int color) {
-        if (index > VERSION_INDEX && index < mColors.length) {
-            mColors[index] = color;
-        } else {
-            Log.e(TAG, "Attempted to set a color at an invalid index " + index);
-        }
-    }
-
-    /**
-     * Encodes {@link #mColors} as a comma-separated String.
-     */
-    String encodeAsString() {
-        StringBuilder colorsStringBuilder = new StringBuilder();
-        for (int color : mColors) {
-            colorsStringBuilder.append(color).append(COLOR_SEPARATOR);
-        }
-        return colorsStringBuilder.toString();
-    }
-
-    /**
-     * Loads colors and wallpaper id from {@link Utilities#getPrefs(Context)}.
-     * These were saved there in {@link ColorExtractionService}.
-     */
-    public void load(Context context) {
-        String encodedString = Utilities.getPrefs(context).getString(
-                ExtractionUtils.EXTRACTED_COLORS_PREFERENCE_KEY, VERSION + "");
-
-        String[] splitColorsString = encodedString.split(COLOR_SEPARATOR);
-        if (splitColorsString.length == DEFAULT_VALUES.length &&
-                Integer.parseInt(splitColorsString[VERSION_INDEX]) == VERSION) {
-            // Parse and apply the saved values.
-            for (int i = 0; i < mColors.length; i++) {
-                mColors[i] = Integer.parseInt(splitColorsString[i]);
-            }
-        } else {
-            // Leave the values as default values as the saved values may not be compatible.
-            ExtractionUtils.startColorExtractionService(context);
-        }
-    }
-
-    /** @param index must be one of the index values defined at the top of this class. */
-    public int getColor(int index) {
-        return mColors[index];
-    }
-
-    /**
-     * The hotseat's color is defined as follows:
-     * - 12% black for super light wallpaper
-     * - 18% white for super dark
-     * - 25% white otherwise
-     */
-    public void updateHotseatPalette(Palette hotseatPalette) {
-        int hotseatColor;
-        if (hotseatPalette != null && ExtractionUtils.isSuperLight(hotseatPalette)) {
-            hotseatColor = ColorUtils.setAlphaComponent(Color.BLACK, (int) (0.12f * 255));
-        } else if (hotseatPalette != null && ExtractionUtils.isSuperDark(hotseatPalette)) {
-            hotseatColor = ColorUtils.setAlphaComponent(Color.WHITE, (int) (0.18f * 255));
-        } else {
-            hotseatColor = DEFAULT_VALUES[HOTSEAT_INDEX];
-        }
-        setColorAtIndex(HOTSEAT_INDEX, hotseatColor);
-    }
-
-    public void updateStatusBarPalette(Palette statusBarPalette) {
-        setColorAtIndex(STATUS_BAR_INDEX, ExtractionUtils.isSuperLight(statusBarPalette) ?
-                DEFAULT_LIGHT : DEFAULT_DARK);
-    }
-
-    public void updateWallpaperThemePalette(@Nullable Palette wallpaperPalette) {
-        int defaultColor = DEFAULT_VALUES[WALLPAPER_VIBRANT_INDEX];
-        setColorAtIndex(WALLPAPER_VIBRANT_INDEX, wallpaperPalette == null
-                ? defaultColor : wallpaperPalette.getVibrantColor(defaultColor));
-    }
-
-    public void addOnChangeListener(OnChangeListener listener) {
-        mListeners.add(listener);
-    }
-
-    public void notifyChange() {
-        for (OnChangeListener listener : mListeners) {
-            listener.onExtractedColorsChanged();
-        }
-    }
-
-    /**
-     * Interface for listening for extracted color changes
-     */
-    public interface OnChangeListener {
-
-        void onExtractedColorsChanged();
-    }
-}
diff --git a/src/com/android/launcher3/dynamicui/ExtractionUtils.java b/src/com/android/launcher3/dynamicui/ExtractionUtils.java
deleted file mode 100644
index cc0e0be..0000000
--- a/src/com/android/launcher3/dynamicui/ExtractionUtils.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright (C) 2016 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.dynamicui;
-
-import android.annotation.TargetApi;
-import android.app.WallpaperManager;
-import android.app.job.JobInfo;
-import android.app.job.JobScheduler;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.graphics.Color;
-import android.os.Build;
-import android.support.v4.graphics.ColorUtils;
-import android.support.v7.graphics.Palette;
-
-import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
-
-import java.util.List;
-
-/**
- * Contains helper fields and methods related to extracting colors from the wallpaper.
- */
-public class ExtractionUtils {
-    public static final String EXTRACTED_COLORS_PREFERENCE_KEY = "pref_extractedColors";
-    public static final String WALLPAPER_ID_PREFERENCE_KEY = "pref_wallpaperId";
-
-    private static final float MIN_CONTRAST_RATIO = 2f;
-
-    /**
-     * Extract colors in the :wallpaper-chooser process, if the wallpaper id has changed.
-     * When the new colors are saved in the LauncherProvider,
-     * Launcher will be notified in Launcher#onSettingsChanged(String, String).
-     */
-    public static void startColorExtractionServiceIfNecessary(final Context context) {
-        if (FeatureFlags.LAUNCHER3_GRADIENT_ALL_APPS) {
-            return;
-        }
-        // Run on a background thread, since the service is asynchronous anyway.
-        Utilities.THREAD_POOL_EXECUTOR.execute(new Runnable() {
-            @Override
-            public void run() {
-                if (hasWallpaperIdChanged(context)) {
-                    startColorExtractionService(context);
-                }
-            }
-        });
-    }
-
-    /** Starts the {@link ColorExtractionService} without checking the wallpaper id */
-    public static void startColorExtractionService(Context context) {
-        if (FeatureFlags.LAUNCHER3_GRADIENT_ALL_APPS) {
-            return;
-        }
-        JobScheduler jobScheduler = (JobScheduler) context.getSystemService(
-                Context.JOB_SCHEDULER_SERVICE);
-        jobScheduler.schedule(new JobInfo.Builder(Utilities.COLOR_EXTRACTION_JOB_ID,
-                new ComponentName(context, ColorExtractionService.class))
-                .setMinimumLatency(0).build());
-    }
-
-    private static boolean hasWallpaperIdChanged(Context context) {
-        if (!Utilities.ATLEAST_NOUGAT) {
-            // TODO: update an id in sharedprefs in onWallpaperChanged broadcast, and read it here.
-            return false;
-        }
-        final SharedPreferences sharedPrefs = Utilities.getPrefs(context);
-        int wallpaperId = getWallpaperId(WallpaperManager.getInstance(context));
-        int savedWallpaperId = sharedPrefs.getInt(ExtractionUtils.WALLPAPER_ID_PREFERENCE_KEY, -1);
-        return wallpaperId != savedWallpaperId;
-    }
-
-    @TargetApi(Build.VERSION_CODES.N)
-    public static int getWallpaperId(WallpaperManager wallpaperManager) {
-        return Utilities.ATLEAST_NOUGAT ?
-                wallpaperManager.getWallpaperId(WallpaperManager.FLAG_SYSTEM) : -1;
-    }
-
-    public static boolean isSuperLight(Palette p) {
-        return !isLegibleOnWallpaper(Color.WHITE, p.getSwatches());
-    }
-
-    public static boolean isSuperDark(Palette p) {
-        return !isLegibleOnWallpaper(Color.BLACK, p.getSwatches());
-    }
-
-    /**
-     * Given a color, returns true if that color is legible on
-     * the given wallpaper color swatches, else returns false.
-     */
-    private static boolean isLegibleOnWallpaper(int color, List<Palette.Swatch> wallpaperSwatches) {
-        int legiblePopulation = 0;
-        int illegiblePopulation = 0;
-        for (Palette.Swatch swatch : wallpaperSwatches) {
-            if (isLegible(color, swatch.getRgb())) {
-                legiblePopulation += swatch.getPopulation();
-            } else {
-                illegiblePopulation += swatch.getPopulation();
-            }
-        }
-        return legiblePopulation > illegiblePopulation;
-    }
-
-    /** @return Whether the foreground color is legible on the background color. */
-    private static boolean isLegible(int foreground, int background) {
-        background = ColorUtils.setAlphaComponent(background, 255);
-        return ColorUtils.calculateContrast(foreground, background) >= MIN_CONTRAST_RATIO;
-    }
-
-}
diff --git a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
index f25345e..5954efa 100644
--- a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
+++ b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
@@ -1,9 +1,8 @@
 package com.android.launcher3.folder;
 
+public class ClippedFolderIconLayoutRule {
 
-public class ClippedFolderIconLayoutRule implements FolderIcon.PreviewLayoutRule {
-
-    static final int MAX_NUM_ITEMS_IN_PREVIEW = 4;
+    public static final int MAX_NUM_ITEMS_IN_PREVIEW = 4;
     private static final int MIN_NUM_ITEMS_IN_PREVIEW = 2;
 
     private static final float MIN_SCALE = 0.48f;
@@ -11,8 +10,8 @@
     private static final float MAX_RADIUS_DILATION = 0.15f;
     private static final float ITEM_RADIUS_SCALE_FACTOR = 1.33f;
 
-    private static final int EXIT_INDEX = -2;
-    private static final int ENTER_INDEX = -3;
+    public static final int EXIT_INDEX = -2;
+    public static final int ENTER_INDEX = -3;
 
     private float[] mTmpPoint = new float[2];
 
@@ -22,7 +21,6 @@
     private boolean mIsRtl;
     private float mBaselineIconScale;
 
-    @Override
     public void init(int availableSpace, float intrinsicIconSize, boolean rtl) {
         mAvailableSpace = availableSpace;
         mRadius = ITEM_RADIUS_SCALE_FACTOR * availableSpace / 2f;
@@ -31,19 +29,18 @@
         mBaselineIconScale = availableSpace / (intrinsicIconSize * 1f);
     }
 
-    @Override
     public PreviewItemDrawingParams computePreviewItemDrawingParams(int index, int curNumItems,
             PreviewItemDrawingParams params) {
-        float totalScale = scaleForItem(index, curNumItems);
+        float totalScale = scaleForItem(curNumItems);
         float transX;
         float transY;
         float overlayAlpha = 0;
 
-        if (index == getExitIndex()) {
+        if (index == EXIT_INDEX) {
             // 0 1 * <-- Exit position (row 0, col 2)
             // 2 3
             getGridPosition(0, 2, mTmpPoint);
-        } else if (index == getEnterIndex()) {
+        } else if (index == ENTER_INDEX) {
             // 0 1
             // 2 3 * <-- Enter position (row 1, col 2)
             getGridPosition(1, 2, mTmpPoint);
@@ -120,7 +117,7 @@
                 MIN_NUM_ITEMS_IN_PREVIEW) / (MAX_NUM_ITEMS_IN_PREVIEW - MIN_NUM_ITEMS_IN_PREVIEW));
         double theta = theta0 + index * (2 * Math.PI / curNumItems) * direction;
 
-        float halfIconSize = (mIconSize * scaleForItem(index, curNumItems)) / 2;
+        float halfIconSize = (mIconSize * scaleForItem(curNumItems)) / 2;
 
         // Map the location along the circle, and offset the coordinates to represent the center
         // of the icon, and to be based from the top / left of the preview area. The y component
@@ -130,10 +127,9 @@
 
     }
 
-    @Override
-    public float scaleForItem(int index, int numItems) {
+    public float scaleForItem(int numItems) {
         // Scale is determined by the number of items in the preview.
-        float scale = 1f;
+        final float scale;
         if (numItems <= 2) {
             scale = MAX_SCALE;
         } else if (numItems == 3) {
@@ -141,37 +137,10 @@
         } else {
             scale = MIN_SCALE;
         }
-
         return scale * mBaselineIconScale;
     }
 
-    @Override
     public float getIconSize() {
         return mIconSize;
     }
-
-    @Override
-    public int maxNumItems() {
-        return MAX_NUM_ITEMS_IN_PREVIEW;
-    }
-
-    @Override
-    public boolean clipToBackground() {
-        return true;
-    }
-
-    @Override
-    public boolean hasEnterExitIndices() {
-        return true;
-    }
-
-    @Override
-    public int getExitIndex() {
-        return EXIT_INDEX;
-    }
-
-    @Override
-    public int getEnterIndex() {
-        return ENTER_INDEX;
-    }
 }
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 85792d4..2168001 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -16,11 +16,12 @@
 
 package com.android.launcher3.folder;
 
+import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
+import static com.android.launcher3.LauncherState.NORMAL;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.animation.PropertyValuesHolder;
 import android.annotation.SuppressLint;
 import android.content.Context;
 import android.content.res.Resources;
@@ -34,10 +35,10 @@
 import android.view.KeyEvent;
 import android.view.Menu;
 import android.view.MenuItem;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewDebug;
 import android.view.accessibility.AccessibilityEvent;
-import android.view.animation.AccelerateInterpolator;
 import android.view.animation.AnimationUtils;
 import android.view.inputmethod.EditorInfo;
 import android.widget.TextView;
@@ -55,24 +56,20 @@
 import com.android.launcher3.FolderInfo.FolderListener;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.LogDecelerateInterpolator;
 import com.android.launcher3.OnAlarmListener;
 import com.android.launcher3.PagedView;
 import com.android.launcher3.R;
 import com.android.launcher3.ShortcutInfo;
-import com.android.launcher3.UninstallDropTarget.DropTargetSource;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace.ItemOperator;
 import com.android.launcher3.accessibility.AccessibleDragListenerAdapter;
-import com.android.launcher3.anim.AnimationLayerSet;
-import com.android.launcher3.anim.CircleRevealOutlineProvider;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragController.DragListener;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.logging.LoggerUtils;
 import com.android.launcher3.pageindicators.PageIndicatorDots;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
@@ -89,8 +86,7 @@
  */
 public class Folder extends AbstractFloatingView implements DragSource, View.OnClickListener,
         View.OnLongClickListener, DropTarget, FolderListener, TextView.OnEditorActionListener,
-        View.OnFocusChangeListener, DragListener, DropTargetSource,
-        ExtendedEditText.OnBackKeyListener {
+        View.OnFocusChangeListener, DragListener, ExtendedEditText.OnBackKeyListener {
     private static final String TAG = "Launcher.Folder";
 
     /**
@@ -137,10 +133,6 @@
 
     private AnimatorSet mCurrentAnimator;
 
-    private final int mExpandDuration;
-    public final int mMaterialExpandDuration;
-    private final int mMaterialExpandStagger;
-
     protected final Launcher mLauncher;
     protected DragController mDragController;
     public FolderInfo mInfo;
@@ -181,10 +173,6 @@
     @ViewDebug.ExportedProperty(category = "launcher")
     private boolean mDestroyed;
 
-    @Thunk Runnable mDeferredAction;
-    private boolean mDeferDropAfterUninstall;
-    private boolean mUninstallSuccessful;
-
     // Folder scrolling
     private int mScrollAreaOffset;
 
@@ -201,9 +189,6 @@
         super(context, attrs);
         setAlwaysDrawnWithCacheEnabled(false);
         Resources res = getResources();
-        mExpandDuration = res.getInteger(R.integer.config_folderExpandDuration);
-        mMaterialExpandDuration = res.getInteger(R.integer.config_materialFolderExpandDuration);
-        mMaterialExpandStagger = res.getInteger(R.integer.config_materialFolderExpandStagger);
 
         if (sDefaultFolderName == null) {
             sDefaultFolderName = res.getString(R.string.folder_name);
@@ -382,11 +367,6 @@
         return false;
     }
 
-    @Override
-    public ExtendedEditText getActiveTextView() {
-        return isEditingName() ? mFolderName : null;
-    }
-
     public FolderIcon getFolderIcon() {
         return mFolderIcon;
     }
@@ -487,25 +467,6 @@
                         ? R.layout.user_folder : R.layout.user_folder_icon_normalized, null);
     }
 
-    /**
-     * This method is intended to make the UserFolder to be visually identical in size and position
-     * to its associated FolderIcon. This allows for a seamless transition into the expanded state.
-     */
-    private void positionAndSizeAsIcon() {
-        if (!(getParent() instanceof DragLayer)) return;
-        setScaleX(0.8f);
-        setScaleY(0.8f);
-        setAlpha(0f);
-        mState = STATE_SMALL;
-    }
-
-    private void prepareReveal() {
-        setScaleX(1f);
-        setScaleY(1f);
-        setAlpha(1f);
-        mState = STATE_SMALL;
-    }
-
     private void startAnimation(final AnimatorSet a) {
         if (mCurrentAnimator != null && mCurrentAnimator.isRunning()) {
             mCurrentAnimator.cancel();
@@ -525,61 +486,6 @@
         a.start();
     }
 
-    private AnimatorSet getOpeningAnimator() {
-        prepareReveal();
-        mFolderIcon.growAndFadeOut();
-
-        AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
-
-        int width = getFolderWidth();
-        int height = getFolderHeight();
-
-        float transX = - 0.075f * (width / 2 - getPivotX());
-        float transY = - 0.075f * (height / 2 - getPivotY());
-        setTranslationX(transX);
-        setTranslationY(transY);
-        PropertyValuesHolder tx = PropertyValuesHolder.ofFloat(TRANSLATION_X, transX, 0);
-        PropertyValuesHolder ty = PropertyValuesHolder.ofFloat(TRANSLATION_Y, transY, 0);
-
-        Animator drift = ObjectAnimator.ofPropertyValuesHolder(this, tx, ty);
-        drift.setDuration(mMaterialExpandDuration);
-        drift.setStartDelay(mMaterialExpandStagger);
-        drift.setInterpolator(new LogDecelerateInterpolator(100, 0));
-
-        int rx = (int) Math.max(Math.max(width - getPivotX(), 0), getPivotX());
-        int ry = (int) Math.max(Math.max(height - getPivotY(), 0), getPivotY());
-        float radius = (float) Math.hypot(rx, ry);
-
-        Animator reveal = new CircleRevealOutlineProvider((int) getPivotX(),
-                (int) getPivotY(), 0, radius).createRevealAnimator(this);
-        reveal.setDuration(mMaterialExpandDuration);
-        reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
-
-        mContent.setAlpha(0f);
-        Animator iconsAlpha = ObjectAnimator.ofFloat(mContent, "alpha", 0f, 1f);
-        iconsAlpha.setDuration(mMaterialExpandDuration);
-        iconsAlpha.setStartDelay(mMaterialExpandStagger);
-        iconsAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
-
-        mFooter.setAlpha(0f);
-        Animator textAlpha = ObjectAnimator.ofFloat(mFooter, "alpha", 0f, 1f);
-        textAlpha.setDuration(mMaterialExpandDuration);
-        textAlpha.setStartDelay(mMaterialExpandStagger);
-        textAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
-
-        anim.play(drift);
-        anim.play(iconsAlpha);
-        anim.play(textAlpha);
-        anim.play(reveal);
-
-        AnimationLayerSet layerSet = new AnimationLayerSet();
-        layerSet.addView(mContent);
-        layerSet.addView(mFooter);
-        anim.addListener(layerSet);
-
-        return anim;
-    }
-
     /**
      * Opens the user folder described by the specified tag. The opening of the folder
      * is animated relative to the specified View. If the View is null, no animation
@@ -621,9 +527,7 @@
         final Runnable onCompleteRunnable;
         centerAboutIcon();
 
-        AnimatorSet anim = FeatureFlags.LAUNCHER3_NEW_FOLDER_ANIMATION
-                ? new FolderAnimationManager(this, true /* isOpening */).getAnimator()
-                : getOpeningAnimator();
+        AnimatorSet anim = new FolderAnimationManager(this, true /* isOpening */).getAnimator();
         onCompleteRunnable = new Runnable() {
             @Override
             public void run() {
@@ -633,12 +537,8 @@
         anim.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationStart(Animator animation) {
-                if (FeatureFlags.LAUNCHER3_NEW_FOLDER_ANIMATION) {
-                    mFolderIcon.setBackgroundVisible(false);
-                    mFolderIcon.drawLeaveBehindIfExists();
-                } else {
-                    mFolderIcon.setVisibility(INVISIBLE);
-                }
+                mFolderIcon.setBackgroundVisible(false);
+                mFolderIcon.drawLeaveBehindIfExists();
 
                 Utilities.sendCustomAccessibilityEvent(
                         Folder.this,
@@ -728,11 +628,7 @@
         }
 
         if (mFolderIcon != null) {
-            if (FeatureFlags.LAUNCHER3_NEW_FOLDER_ANIMATION) {
-                mFolderIcon.clearLeaveBehindIfExists();
-            } else {
-                mFolderIcon.shrinkAndFadeIn(animate);
-            }
+            mFolderIcon.clearLeaveBehindIfExists();
         }
 
         if (!(getParent() instanceof DragLayer)) return;
@@ -749,21 +645,8 @@
         parent.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
     }
 
-    private AnimatorSet getClosingAnimator() {
-        AnimatorSet animatorSet = LauncherAnimUtils.createAnimatorSet();
-        animatorSet.play(LauncherAnimUtils.ofViewAlphaAndScale(this, 0, 0.9f, 0.9f));
-
-        AnimationLayerSet layerSet = new AnimationLayerSet();
-        layerSet.addView(this);
-        animatorSet.addListener(layerSet);
-        animatorSet.setDuration(mExpandDuration);
-        return animatorSet;
-    }
-
     private void animateClosed() {
-        AnimatorSet a = FeatureFlags.LAUNCHER3_NEW_FOLDER_ANIMATION
-                ? new FolderAnimationManager(this, false /* isOpening */).getAnimator()
-                : getClosingAnimator();
+        AnimatorSet a = new FolderAnimationManager(this, false /* isOpening */).getAnimator();
         a.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
@@ -790,16 +673,12 @@
         clearFocus();
         if (mFolderIcon != null) {
             mFolderIcon.setVisibility(View.VISIBLE);
-            if (FeatureFlags.LAUNCHER3_NEW_FOLDER_ANIMATION) {
-                mFolderIcon.setBackgroundVisible(true);
-                mFolderIcon.mFolderName.setTextVisibility(true);
-            }
+            mFolderIcon.setBackgroundVisible(true);
+            mFolderIcon.mFolderName.setTextVisibility(true);
             if (wasAnimated) {
-                if (FeatureFlags.LAUNCHER3_NEW_FOLDER_ANIMATION) {
-                    mFolderIcon.mBackground.fadeInBackgroundShadow();
-                    mFolderIcon.mBackground.animateBackgroundStroke();
-                    mFolderIcon.onFolderClose(mContent.getCurrentPage());
-                }
+                mFolderIcon.mBackground.fadeInBackgroundShadow();
+                mFolderIcon.mBackground.animateBackgroundStroke();
+                mFolderIcon.onFolderClose(mContent.getCurrentPage());
                 if (mFolderIcon.hasBadge()) {
                     mFolderIcon.createBadgeScaleAnimator(0f, 1f).start();
                 }
@@ -824,6 +703,7 @@
         mContent.setCurrentPage(0);
     }
 
+    @Override
     public boolean acceptDrop(DragObject d) {
         final ItemInfo item = d.dragInfo;
         final int itemType = item.itemType;
@@ -852,18 +732,14 @@
         return (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
     }
 
-    @Override
-    public void onDragOver(DragObject d) {
-        onDragOver(d, REORDER_DELAY);
-    }
-
     private int getTargetRank(DragObject d, float[] recycle) {
         recycle = d.getVisualCenter(recycle);
         return mContent.findNearestArea(
                 (int) recycle[0] - getPaddingLeft(), (int) recycle[1] - getPaddingTop());
     }
 
-    @Thunk void onDragOver(DragObject d, int reorderDelay) {
+    @Override
+    public void onDragOver(DragObject d) {
         if (mScrollPauseAlarm.alarmPending()) {
             return;
         }
@@ -976,23 +852,9 @@
     }
 
     public void onDropCompleted(final View target, final DragObject d,
-            final boolean isFlingToDelete, final boolean success) {
-        if (mDeferDropAfterUninstall) {
-            Log.d(TAG, "Deferred handling drop because waiting for uninstall.");
-            mDeferredAction = new Runnable() {
-                    public void run() {
-                        onDropCompleted(target, d, isFlingToDelete, success);
-                        mDeferredAction = null;
-                    }
-                };
-            return;
-        }
+            final boolean success) {
 
-        boolean beingCalledAfterUninstall = mDeferredAction != null;
-        boolean successfulDrop =
-                success && (!beingCalledAfterUninstall || mUninstallSuccessful);
-
-        if (successfulDrop) {
+        if (success) {
             if (mDeleteFolderOnDropCompleted && !mItemAddedBackToSelfViaIcon && target != this) {
                 replaceFolderWithFinalItem();
             }
@@ -1007,14 +869,14 @@
             mItemsInvalidated = true;
 
             try (SuppressInfoChanges s = new SuppressInfoChanges()) {
-                mFolderIcon.onDrop(d);
+                mFolderIcon.onDrop(d, true /* itemReturnedOnFailedDrop */);
             }
         }
 
         if (target != this) {
             if (mOnExitAlarm.alarmPending()) {
                 mOnExitAlarm.cancelAlarm();
-                if (!successfulDrop) {
+                if (!success) {
                     mSuppressFolderDeletion = true;
                 }
                 mScrollPauseAlarm.cancelAlarm();
@@ -1038,41 +900,6 @@
             mInfo.setOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION, false,
                     mLauncher.getModelWriter());
         }
-
-        if (!isFlingToDelete) {
-            // Fling to delete already exits spring loaded mode after the animation finishes.
-            mLauncher.exitSpringLoadedDragModeDelayed(successfulDrop,
-                    Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
-        }
-    }
-
-    @Override
-    public void deferCompleteDropAfterUninstallActivity() {
-        mDeferDropAfterUninstall = true;
-    }
-
-    @Override
-    public void onDragObjectRemoved(boolean success) {
-        mDeferDropAfterUninstall = false;
-        mUninstallSuccessful = success;
-        if (mDeferredAction != null) {
-            mDeferredAction.run();
-        }
-    }
-
-    @Override
-    public float getIntrinsicIconScaleFactor() {
-        return 1f;
-    }
-
-    @Override
-    public boolean supportsAppInfoDropTarget() {
-        return true;
-    }
-
-    @Override
-    public boolean supportsDeleteDropTarget() {
-        return true;
     }
 
     private void updateItemLocationsInDatabaseBatch() {
@@ -1095,10 +922,7 @@
     }
 
     public boolean isDropEnabled() {
-        if (FeatureFlags.LAUNCHER3_NEW_FOLDER_ANIMATION) {
-            return mState != STATE_ANIMATING;
-        }
-        return true;
+        return mState != STATE_ANIMATING;
     }
 
     public boolean isFull() {
@@ -1113,7 +937,7 @@
         int width = getFolderWidth();
         int height = getFolderHeight();
 
-        float scale = parent.getDescendantRectRelativeToSelf(mFolderIcon, sTempRect);
+        parent.getDescendantRectRelativeToSelf(mFolderIcon, sTempRect);
         int centerX = sTempRect.centerX();
         int centerY = sTempRect.centerY();
         int centeredLeft = centerX - width / 2;
@@ -1329,22 +1153,7 @@
         }
     }
 
-    public void onDrop(DragObject d) {
-        Runnable cleanUpRunnable = null;
-
-        // If we are coming from All Apps space, we defer removing the extra empty screen
-        // until the folder closes
-        if (d.dragSource != mLauncher.getWorkspace() && !(d.dragSource instanceof Folder)) {
-            cleanUpRunnable = new Runnable() {
-                @Override
-                public void run() {
-                    mLauncher.exitSpringLoadedDragModeDelayed(true,
-                            Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT,
-                            null);
-                }
-            };
-        }
-
+    public void onDrop(DragObject d, DragOptions options) {
         // If the icon was dropped while the page was being scrolled, we need to compute
         // the target location again such that the icon is placed of the final page.
         if (!mContent.rankOnCurrentPage(mEmptyCellRank)) {
@@ -1410,8 +1219,7 @@
                 float scaleY = getScaleY();
                 setScaleX(1.0f);
                 setScaleY(1.0f);
-                mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, currentDragView,
-                        cleanUpRunnable, null);
+                mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, currentDragView, null);
                 setScaleX(scaleX);
                 setScaleY(scaleY);
             } else {
@@ -1436,6 +1244,7 @@
             mInfo.setOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION, true, mLauncher.getModelWriter());
         }
 
+        mLauncher.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
         if (d.stateAnnouncer != null) {
             d.stateAnnouncer.completeAction(R.string.item_moved);
         }
@@ -1611,7 +1420,7 @@
         @Override
         public void onAlarm(Alarm alarm) {
             // Reorder immediately on page change.
-            onDragOver(mDragObject, 1);
+            onDragOver(mDragObject);
         }
     }
 
@@ -1654,7 +1463,45 @@
     }
 
     @Override
-    public int getLogContainerType() {
-        return ContainerType.FOLDER;
+    public void logActionCommand(int command) {
+        mLauncher.getUserEventDispatcher().logActionCommand(
+                command, getFolderIcon(), ContainerType.FOLDER);
+    }
+
+    @Override
+    public void onBackPressed() {
+        if (isEditingName()) {
+            mFolderName.dispatchBackKey();
+        } else {
+            super.onBackPressed();
+        }
+    }
+
+    @Override
+    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+            DragLayer dl = mLauncher.getDragLayer();
+
+            if (isEditingName()) {
+                if (!dl.isEventOverView(mFolderName, ev)) {
+                    mFolderName.dispatchBackKey();
+                    return true;
+                }
+                return false;
+            } else if (!dl.isEventOverView(this, ev)) {
+                if (mLauncher.getAccessibilityDelegate().isInAccessibleDrag()) {
+                    // Do not close the container if in drag and drop.
+                    if (!dl.isEventOverView(mLauncher.getDropTargetBar(), ev)) {
+                        return true;
+                    }
+                } else {
+                    mLauncher.getUserEventDispatcher().logActionTapOutside(
+                            LoggerUtils.newContainerTarget(ContainerType.FOLDER));
+                    close(true);
+                    return true;
+                }
+            }
+        }
+        return false;
     }
 }
diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java
index 69705d5..ec448e9 100644
--- a/src/com/android/launcher3/folder/FolderAnimationManager.java
+++ b/src/com/android/launcher3/folder/FolderAnimationManager.java
@@ -16,12 +16,16 @@
 
 package com.android.launcher3.folder;
 
+import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
 import android.content.Context;
+import android.content.res.Resources;
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.graphics.drawable.GradientDrawable;
@@ -74,19 +78,6 @@
 
     private final PreviewItemDrawingParams mTmpParams = new PreviewItemDrawingParams(0, 0, 0, 0);
 
-    private static final Property<View, Float> SCALE_PROPERTY =
-            new Property<View, Float>(Float.class, "scale") {
-                @Override
-                public Float get(View view) {
-                    return view.getScaleX();
-                }
-
-                @Override
-                public void set(View view, Float scale) {
-                    view.setScaleX(scale);
-                    view.setScaleY(scale);
-                }
-            };
 
     public FolderAnimationManager(Folder folder, boolean isOpening) {
         mFolder = folder;
@@ -101,8 +92,9 @@
 
         mIsOpening = isOpening;
 
-        mDuration = mFolder.mMaterialExpandDuration;
-        mDelay = mContext.getResources().getInteger(R.integer.config_folderDelay);
+        Resources res = mContent.getResources();
+        mDuration = res.getInteger(R.integer.config_materialFolderExpandDuration);
+        mDelay = res.getInteger(R.integer.config_folderDelay);
 
         mFolderInterpolator = AnimationUtils.loadInterpolator(mContext,
                 R.interpolator.folder_interpolator);
@@ -118,7 +110,7 @@
      */
     public AnimatorSet getAnimator() {
         final DragLayer.LayoutParams lp = (DragLayer.LayoutParams) mFolder.getLayoutParams();
-        FolderIcon.PreviewLayoutRule rule = mFolderIcon.getLayoutRule();
+        ClippedFolderIconLayoutRule rule = mFolderIcon.getLayoutRule();
         final List<BubbleTextView> itemsInPreview = mFolderIcon.getPreviewItems();
 
         // Match position of the FolderIcon
@@ -129,7 +121,7 @@
         float initialSize = (scaledRadius * 2) * scaleRelativeToDragLayer;
 
         // Match size/scale of icons in the preview
-        float previewScale = rule.scaleForItem(0, itemsInPreview.size());
+        float previewScale = rule.scaleForItem(itemsInPreview.size());
         float previewSize = rule.getIconSize() * previewScale;
         float initialScale = previewSize / itemsInPreview.get(0).getIconSize()
                 * scaleRelativeToDragLayer;
@@ -242,15 +234,14 @@
      */
     private void addPreviewItemAnimators(AnimatorSet animatorSet, final float folderScale,
             int previewItemOffsetX, int previewItemOffsetY) {
-        FolderIcon.PreviewLayoutRule rule = mFolderIcon.getLayoutRule();
+        ClippedFolderIconLayoutRule rule = mFolderIcon.getLayoutRule();
         boolean isOnFirstPage = mFolder.mContent.getCurrentPage() == 0;
         final List<BubbleTextView> itemsInPreview = isOnFirstPage
                 ? mFolderIcon.getPreviewItems()
                 : mFolderIcon.getPreviewItemsOnPage(mFolder.mContent.getCurrentPage());
         final int numItemsInPreview = itemsInPreview.size();
         final int numItemsInFirstPagePreview = isOnFirstPage
-                ? numItemsInPreview
-                : FolderIcon.NUM_ITEMS_IN_PREVIEW;
+                ? numItemsInPreview : MAX_NUM_ITEMS_IN_PREVIEW;
 
         TimeInterpolator previewItemInterpolator = getPreviewItemInterpolator();
 
@@ -264,7 +255,7 @@
             cwc.setupLp(btv);
 
             // Match scale of icons in the preview of the items on the first page.
-            float previewScale = rule.scaleForItem(i, numItemsInFirstPagePreview);
+            float previewScale = rule.scaleForItem(numItemsInFirstPagePreview);
             float previewSize = rule.getIconSize() * previewScale;
             float iconScale = previewSize / itemsInPreview.get(i).getIconSize();
 
@@ -299,7 +290,7 @@
             scaleAnimator.setInterpolator(previewItemInterpolator);
             play(animatorSet, scaleAnimator);
 
-            if (mFolder.getItemCount() > FolderIcon.NUM_ITEMS_IN_PREVIEW) {
+            if (mFolder.getItemCount() > MAX_NUM_ITEMS_IN_PREVIEW) {
                 // These delays allows the preview items to move as part of the Folder's motion,
                 // and its only necessary for large folders because of differing interpolators.
                 int delay = mIsOpening ? mDelay : mDelay * 2;
@@ -349,7 +340,7 @@
     }
 
     private TimeInterpolator getPreviewItemInterpolator() {
-        if (mFolder.getItemCount() > FolderIcon.NUM_ITEMS_IN_PREVIEW) {
+        if (mFolder.getItemCount() > MAX_NUM_ITEMS_IN_PREVIEW) {
             // With larger folders, we want the preview items to reach their final positions faster
             // (when opening) and later (when closing) so that they appear aligned with the rest of
             // the folder items when they are both visible.
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 8339bc5..5983029 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -16,8 +16,10 @@
 
 package com.android.launcher3.folder;
 
+import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
+import static com.android.launcher3.folder.PreviewItemManager.INITIAL_ITEM_ANIMATION_DURATION;
+
 import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
 import android.content.Context;
 import android.graphics.Canvas;
@@ -34,8 +36,6 @@
 import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.ViewGroup;
-import android.view.animation.AccelerateInterpolator;
-import android.view.animation.DecelerateInterpolator;
 import android.widget.FrameLayout;
 
 import com.android.launcher3.Alarm;
@@ -49,7 +49,6 @@
 import com.android.launcher3.FolderInfo.FolderListener;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.OnAlarmListener;
 import com.android.launcher3.R;
@@ -58,9 +57,9 @@
 import com.android.launcher3.StylusEventHelper;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
+import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.badge.BadgeRenderer;
 import com.android.launcher3.badge.FolderBadgeInfo;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.BaseItemDragListener;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragView;
@@ -71,8 +70,6 @@
 import java.util.ArrayList;
 import java.util.List;
 
-import static com.android.launcher3.folder.PreviewItemManager.INITIAL_ITEM_ANIMATION_DURATION;
-
 /**
  * An icon that can appear on in the workspace representing an {@link Folder}.
  */
@@ -82,10 +79,6 @@
     private FolderInfo mInfo;
     @Thunk static boolean sStaticValuesDirty = true;
 
-    public static final int NUM_ITEMS_IN_PREVIEW = FeatureFlags.LAUNCHER3_LEGACY_FOLDER_ICON ?
-            StackFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW :
-            ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
-
     private CheckLongPressHelper mLongPressHelper;
     private StylusEventHelper mStylusEventHelper;
 
@@ -103,9 +96,10 @@
     private boolean mBackgroundIsVisible = true;
 
     FolderIconPreviewVerifier mPreviewVerifier;
-    PreviewLayoutRule mPreviewLayoutRule;
+    ClippedFolderIconLayoutRule mPreviewLayoutRule;
     private PreviewItemManager mPreviewItemManager;
     private PreviewItemDrawingParams mTmpParams = new PreviewItemDrawingParams(0, 0, 0, 0);
+    private List<BubbleTextView> mCurrentPreviewItems = new ArrayList<>();
 
     boolean mAnimating = false;
     private Rect mTempBounds = new Rect();
@@ -146,9 +140,7 @@
     private void init() {
         mLongPressHelper = new CheckLongPressHelper(this);
         mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this);
-        mPreviewLayoutRule = FeatureFlags.LAUNCHER3_LEGACY_FOLDER_ICON ?
-                new StackFolderIconLayoutRule() :
-                new ClippedFolderIconLayoutRule();
+        mPreviewLayoutRule = new ClippedFolderIconLayoutRule();
         mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
         mPreviewItemManager = new PreviewItemManager(this);
     }
@@ -206,7 +198,7 @@
     private void setFolder(Folder folder) {
         mFolder = folder;
         mPreviewVerifier = new FolderIconPreviewVerifier(mLauncher.getDeviceProfile().inv);
-        mPreviewItemManager.updateItemDrawingParams(false);
+        updatePreviewItems(false);
     }
 
     private boolean willAcceptItem(ItemInfo item) {
@@ -261,7 +253,7 @@
 
     public void performCreateAnimation(final ShortcutInfo destInfo, final View destView,
             final ShortcutInfo srcInfo, final DragView srcView, Rect dstRect,
-            float scaleRelativeToDragLayer, Runnable postAnimationRunnable) {
+            float scaleRelativeToDragLayer) {
         prepareCreateAnimation(destView);
         addItem(destInfo);
         // This will animate the first item from it's position as an icon into its
@@ -270,7 +262,8 @@
                 .start();
 
         // This will animate the dragView (srcView) into the new folder
-        onDrop(srcInfo, srcView, dstRect, scaleRelativeToDragLayer, 1, postAnimationRunnable);
+        onDrop(srcInfo, srcView, dstRect, scaleRelativeToDragLayer, 1,
+                false /* itemReturnedOnFailedDrop */);
     }
 
     public void performDestroyAnimation(Runnable onCompleteRunnable) {
@@ -285,7 +278,8 @@
     }
 
     private void onDrop(final ShortcutInfo item, DragView animateView, Rect finalRect,
-            float scaleRelativeToDragLayer, int index, Runnable postAnimationRunnable) {
+            float scaleRelativeToDragLayer, int index,
+            boolean itemReturnedOnFailedDrop) {
         item.cellX = -1;
         item.cellY = -1;
 
@@ -301,7 +295,7 @@
                 to = new Rect();
                 Workspace workspace = mLauncher.getWorkspace();
                 // Set cellLayout and this to it's final state to compute final animation locations
-                workspace.setFinalTransitionTransform((CellLayout) getParent().getParent());
+                workspace.setFinalTransitionTransform();
                 float scaleX = getScaleX();
                 float scaleY = getScaleY();
                 setScaleX(1.0f);
@@ -310,25 +304,28 @@
                 // Finished computing final animation locations, restore current state
                 setScaleX(scaleX);
                 setScaleY(scaleY);
-                workspace.resetTransitionTransform((CellLayout) getParent().getParent());
+                workspace.resetTransitionTransform();
             }
 
+            int numItemsInPreview = Math.min(MAX_NUM_ITEMS_IN_PREVIEW, index + 1);
             boolean itemAdded = false;
-            if (index >= mPreviewLayoutRule.maxNumItems()
-                    && mPreviewLayoutRule.hasEnterExitIndices()) {
-                List<BubbleTextView> oldPreviewItems = getPreviewItemsOnPage(0);
+            if (itemReturnedOnFailedDrop || index >= MAX_NUM_ITEMS_IN_PREVIEW) {
+                List<BubbleTextView> oldPreviewItems = new ArrayList<>(mCurrentPreviewItems);
                 addItem(item, false);
-                List<BubbleTextView> newPreviewItems = getPreviewItemsOnPage(0);
+                mCurrentPreviewItems.clear();
+                mCurrentPreviewItems.addAll(getPreviewItems());
 
-                if (!oldPreviewItems.containsAll(newPreviewItems)) {
-                    for (int i = 0; i < newPreviewItems.size(); ++i) {
-                        if (newPreviewItems.get(i).getTag().equals(item)) {
+                if (!oldPreviewItems.equals(mCurrentPreviewItems)) {
+                    for (int i = 0; i < mCurrentPreviewItems.size(); ++i) {
+                        if (mCurrentPreviewItems.get(i).getTag().equals(item)) {
                             // If the item dropped is going to be in the preview, we update the
                             // index here to reflect its position in the preview.
                             index = i;
                         }
                     }
-                    mPreviewItemManager.onDrop(oldPreviewItems, newPreviewItems, item);
+
+                    mPreviewItemManager.hidePreviewItem(index, true);
+                    mPreviewItemManager.onDrop(oldPreviewItems, mCurrentPreviewItems, item);
                     itemAdded = true;
                 } else {
                     removeItem(item, false);
@@ -340,20 +337,20 @@
             }
 
             int[] center = new int[2];
-            float scale = getLocalCenterForIndex(index, index + 1, center);
+            float scale = getLocalCenterForIndex(index, numItemsInPreview, center);
             center[0] = (int) Math.round(scaleRelativeToDragLayer * center[0]);
             center[1] = (int) Math.round(scaleRelativeToDragLayer * center[1]);
 
             to.offset(center[0] - animateView.getMeasuredWidth() / 2,
                     center[1] - animateView.getMeasuredHeight() / 2);
 
-            float finalAlpha = index < mPreviewLayoutRule.maxNumItems() ? 0.5f : 0f;
+            float finalAlpha = index < MAX_NUM_ITEMS_IN_PREVIEW ? 0.5f : 0f;
 
             float finalScale = scale * scaleRelativeToDragLayer;
             dragLayer.animateView(animateView, from, to, finalAlpha,
                     1, 1, finalScale, finalScale, DROP_IN_ANIMATION_DURATION,
-                    new DecelerateInterpolator(2), new AccelerateInterpolator(2),
-                    postAnimationRunnable, DragLayer.ANIMATION_END_DISAPPEAR, null);
+                    Interpolators.DEACCEL_2, Interpolators.ACCEL_2,
+                    null, DragLayer.ANIMATION_END_DISAPPEAR, null);
 
             mFolder.hideItem(item);
 
@@ -371,7 +368,7 @@
         }
     }
 
-    public void onDrop(DragObject d) {
+    public void onDrop(DragObject d, boolean itemReturnedOnFailedDrop) {
         ShortcutInfo item;
         if (d.dragInfo instanceof AppInfo) {
             // Came from all apps -- make a copy
@@ -383,7 +380,8 @@
             item = (ShortcutInfo) d.dragInfo;
         }
         mFolder.notifyDrop();
-        onDrop(item, d.dragView, null, 1.0f, mInfo.contents.size(), d.postAnimationRunnable);
+        onDrop(item, d.dragView, null, 1.0f, mInfo.contents.size(),
+                itemReturnedOnFailedDrop);
     }
 
     public void setBadgeInfo(FolderBadgeInfo badgeInfo) {
@@ -391,7 +389,7 @@
         mBadgeInfo = badgeInfo;
     }
 
-    public PreviewLayoutRule getLayoutRule() {
+    public ClippedFolderIconLayoutRule getLayoutRule() {
         return mPreviewLayoutRule;
     }
 
@@ -420,7 +418,7 @@
 
     private float getLocalCenterForIndex(int index, int curNumItems, int[] center) {
         mTmpParams = mPreviewItemManager.computePreviewItemDrawingParams(
-                Math.min(mPreviewLayoutRule.maxNumItems(), index), curNumItems, mTmpParams);
+                Math.min(MAX_NUM_ITEMS_IN_PREVIEW, index), curNumItems, mTmpParams);
 
         mTmpParams.transX += mBackground.basePreviewOffsetX;
         mTmpParams.transY += mBackground.basePreviewOffsetY;
@@ -474,19 +472,17 @@
                     Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG);
         } else {
             saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
-            if (mPreviewLayoutRule.clipToBackground()) {
-                canvas.clipPath(mBackground.getClipPath(), Region.Op.INTERSECT);
-            }
+            canvas.clipPath(mBackground.getClipPath(), Region.Op.INTERSECT);
         }
 
         mPreviewItemManager.draw(canvas);
 
-        if (mPreviewLayoutRule.clipToBackground() && canvas.isHardwareAccelerated()) {
+        if (canvas.isHardwareAccelerated()) {
             mBackground.clipCanvasHardware(canvas);
         }
         canvas.restoreToCount(saveCount);
 
-        if (mPreviewLayoutRule.clipToBackground() && !mBackground.drawingDelegated()) {
+        if (!mBackground.drawingDelegated()) {
             mBackground.drawBackgroundStroke(canvas);
         }
 
@@ -542,7 +538,7 @@
                 itemsToDisplay.add(itemsOnPage.get(rank));
             }
 
-            if (itemsToDisplay.size() == FolderIcon.NUM_ITEMS_IN_PREVIEW) {
+            if (itemsToDisplay.size() == MAX_NUM_ITEMS_IN_PREVIEW) {
                 break;
             }
         }
@@ -556,11 +552,17 @@
 
     @Override
     public void onItemsChanged(boolean animate) {
-        mPreviewItemManager.updateItemDrawingParams(animate);
+        updatePreviewItems(animate);
         invalidate();
         requestLayout();
     }
 
+    private void updatePreviewItems(boolean animate) {
+        mPreviewItemManager.updatePreviewItems(animate);
+        mCurrentPreviewItems.clear();
+        mCurrentPreviewItems.addAll(getPreviewItems());
+    }
+
     @Override
     public void prepareAutoUpdate() {
     }
@@ -631,30 +633,6 @@
         mInfo.removeListener(mFolder);
     }
 
-    public void shrinkAndFadeIn(boolean animate) {
-        // We remove and re-draw the FolderIcon in-case it has changed
-        final PreviewImageView previewImage = PreviewImageView.get(getContext());
-        previewImage.removeFromParent();
-        copyToPreview(previewImage);
-
-        clearLeaveBehindIfExists();
-
-        ObjectAnimator oa = LauncherAnimUtils.ofViewAlphaAndScale(previewImage, 1, 1, 1);
-        oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration));
-        oa.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                // Remove the ImageView copy of the FolderIcon and make the original visible.
-                previewImage.removeFromParent();
-                setVisibility(View.VISIBLE);
-            }
-        });
-        oa.start();
-        if (!animate) {
-            oa.end();
-        }
-    }
-
     public void clearLeaveBehindIfExists() {
         ((CellLayout.LayoutParams) getLayoutParams()).canReorder = true;
         if (mInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
@@ -673,48 +651,7 @@
         }
     }
 
-    public void growAndFadeOut() {
-        drawLeaveBehindIfExists();
-
-        // Push an ImageView copy of the FolderIcon into the DragLayer and hide the original
-        PreviewImageView previewImage = PreviewImageView.get(getContext());
-        copyToPreview(previewImage);
-        setVisibility(View.INVISIBLE);
-
-        ObjectAnimator oa = LauncherAnimUtils.ofViewAlphaAndScale(previewImage, 0, 1.5f, 1.5f);
-        oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration));
-        oa.start();
-    }
-
-    /**
-     * This method draws the FolderIcon to an ImageView and then adds and positions that ImageView
-     * in the DragLayer in the exact absolute location of the original FolderIcon.
-     */
-    private void copyToPreview(PreviewImageView previewImageView) {
-        previewImageView.copy(this);
-        if (mFolder != null) {
-            previewImageView.setPivotX(mFolder.getPivotXForIconAnimation());
-            previewImageView.setPivotY(mFolder.getPivotYForIconAnimation());
-            mFolder.bringToFront();
-        }
-    }
-
     public void onFolderClose(int currentPage) {
         mPreviewItemManager.onFolderClose(currentPage);
     }
-
-    interface PreviewLayoutRule {
-        PreviewItemDrawingParams computePreviewItemDrawingParams(int index, int curNumItems,
-                PreviewItemDrawingParams params);
-        void init(int availableSpace, float intrinsicIconSize, boolean rtl);
-        float scaleForItem(int index, int totalNumItems);
-        float getIconSize();
-        int maxNumItems();
-        boolean clipToBackground();
-
-        boolean hasEnterExitIndices();
-        int getExitIndex();
-        int getEnterIndex();
-
-    }
 }
diff --git a/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java b/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java
index d054a5d..5a27cd4 100644
--- a/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java
+++ b/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java
@@ -18,7 +18,8 @@
 
 import com.android.launcher3.FolderInfo;
 import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.config.FeatureFlags;
+
+import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
 
 /**
  * Verifies whether an item in a Folder is displayed in the FolderIcon preview.
@@ -45,9 +46,7 @@
                 mMaxGridCountY, mMaxItemsPerPage, mGridSize);
         mGridCountX = mGridSize[0];
 
-        mDisplayingUpperLeftQuadrant = FeatureFlags.LAUNCHER3_NEW_FOLDER_ANIMATION
-                && !FeatureFlags.LAUNCHER3_LEGACY_FOLDER_ICON
-                && numItemsInFolder > FolderIcon.NUM_ITEMS_IN_PREVIEW;
+        mDisplayingUpperLeftQuadrant = numItemsInFolder > MAX_NUM_ITEMS_IN_PREVIEW;
     }
 
     /**
@@ -70,6 +69,6 @@
             int row = rank / mGridCountX;
             return col < 2 && row < 2;
         }
-        return rank < FolderIcon.NUM_ITEMS_IN_PREVIEW;
+        return rank < MAX_NUM_ITEMS_IN_PREVIEW;
     }
 }
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index f4ac0a1..9e5bc4f 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -27,7 +27,6 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewDebug;
-import android.view.animation.DecelerateInterpolator;
 
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.CellLayout;
@@ -43,6 +42,7 @@
 import com.android.launcher3.ShortcutInfo;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace.ItemOperator;
+import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.keyboard.ViewGroupFocusHelper;
 import com.android.launcher3.pageindicators.PageIndicator;
 import com.android.launcher3.util.Thunk;
@@ -511,7 +511,7 @@
         int scroll = getScrollForPage(getNextPage()) + hint;
         int delta = scroll - getScrollX();
         if (delta != 0) {
-            mScroller.setInterpolator(new DecelerateInterpolator());
+            mScroller.setInterpolator(Interpolators.DEACCEL);
             mScroller.startScroll(getScrollX(), 0, delta, 0, Folder.SCROLL_HINT_DURATION);
             invalidate();
         }
diff --git a/src/com/android/launcher3/folder/PreviewImageView.java b/src/com/android/launcher3/folder/PreviewImageView.java
deleted file mode 100644
index 65d9db1..0000000
--- a/src/com/android/launcher3/folder/PreviewImageView.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2016 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.folder;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.PorterDuff;
-import android.graphics.Rect;
-import android.view.View;
-import android.widget.ImageView;
-
-import com.android.launcher3.Launcher;
-import com.android.launcher3.R;
-import com.android.launcher3.dragndrop.DragLayer;
-
-/**
- * A temporary view which displays the a bitmap (used for folder icon animation)
- */
-public class PreviewImageView extends ImageView {
-
-    private final Rect mTempRect = new Rect();
-    private final DragLayer mParent;
-
-    private Bitmap mBitmap;
-    private Canvas mCanvas;
-
-    public PreviewImageView(DragLayer parent) {
-        super(parent.getContext());
-        mParent = parent;
-    }
-
-    public void copy(View view) {
-        final int width = view.getMeasuredWidth();
-        final int height = view.getMeasuredHeight();
-
-        if (mBitmap == null || mBitmap.getWidth() != width || mBitmap.getHeight() != height) {
-            mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
-            mCanvas = new Canvas(mBitmap);
-        }
-
-        DragLayer.LayoutParams lp;
-        if (getLayoutParams() instanceof DragLayer.LayoutParams) {
-            lp = (DragLayer.LayoutParams) getLayoutParams();
-        } else {
-            lp = new DragLayer.LayoutParams(width, height);
-        }
-
-        // The layout from which the folder is being opened may be scaled, adjust the starting
-        // view size by this scale factor.
-        float scale = mParent.getDescendantRectRelativeToSelf(view, mTempRect);
-        lp.customPosition = true;
-        lp.x = mTempRect.left;
-        lp.y = mTempRect.top;
-        lp.width = (int) (scale * width);
-        lp.height = (int) (scale * height);
-
-        mCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
-        view.draw(mCanvas);
-        setImageBitmap(mBitmap);
-
-        // Just in case this image view is still in the drag layer from a previous animation,
-        // we remove it and re-add it.
-        removeFromParent();
-        mParent.addView(this, lp);
-    }
-
-    public void removeFromParent() {
-        if (mParent.indexOfChild(this) != -1) {
-            mParent.removeView(this);
-        }
-    }
-
-    public static PreviewImageView get(Context context) {
-        DragLayer dragLayer = Launcher.getLauncher(context).getDragLayer();
-        PreviewImageView view = (PreviewImageView) dragLayer.getTag(R.id.preview_image_id);
-        if (view == null) {
-            view = new PreviewImageView(dragLayer);
-            dragLayer.setTag(R.id.preview_image_id, view);
-        }
-        return view;
-    }
-}
diff --git a/src/com/android/launcher3/folder/PreviewItemManager.java b/src/com/android/launcher3/folder/PreviewItemManager.java
index 2d979a6..06d3eb1 100644
--- a/src/com/android/launcher3/folder/PreviewItemManager.java
+++ b/src/com/android/launcher3/folder/PreviewItemManager.java
@@ -29,11 +29,13 @@
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.ShortcutInfo;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
 
 import java.util.ArrayList;
 import java.util.List;
 
+import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ENTER_INDEX;
+import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.EXIT_INDEX;
+import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
 import static com.android.launcher3.folder.FolderIcon.DROP_IN_ANIMATION_DURATION;
 
 /**
@@ -108,7 +110,7 @@
             mIcon.mPreviewLayoutRule.init(mIcon.mBackground.previewSize, mIntrinsicIconSize,
                     Utilities.isRtl(mIcon.getResources()));
 
-            updateItemDrawingParams(false);
+            updatePreviewItems(false);
         }
     }
 
@@ -183,6 +185,11 @@
     }
 
     public void hidePreviewItem(int index, boolean hidden) {
+        // If there are more params than visible in the preview, they are used for enter/exit
+        // animation purposes and they were added to the front of the list.
+        // To index the params properly, we need to skip these params.
+        index = index + Math.max(mFirstPageParams.size() - MAX_NUM_ITEMS_IN_PREVIEW, 0);
+
         PreviewItemDrawingParams params = index < mFirstPageParams.size() ?
                 mFirstPageParams.get(index) : null;
         if (params != null) {
@@ -202,7 +209,7 @@
             params.add(new PreviewItemDrawingParams(0, 0, 0, 0));
         }
 
-        int numItemsInFirstPagePreview = page == 0 ? items.size() : FolderIcon.NUM_ITEMS_IN_PREVIEW;
+        int numItemsInFirstPagePreview = page == 0 ? items.size() : MAX_NUM_ITEMS_IN_PREVIEW;
         for (int i = 0; i < params.size(); i++) {
             PreviewItemDrawingParams p = params.get(i);
             p.drawable = items.get(i).getCompoundDrawables()[1];
@@ -213,7 +220,7 @@
                 p.drawable.setCallback(mIcon);
             }
 
-            if (!animate || FeatureFlags.LAUNCHER3_LEGACY_FOLDER_ICON) {
+            if (!animate) {
                 computePreviewItemDrawingParams(i, numItemsInFirstPagePreview, p);
                 if (mReferenceDrawable == null) {
                     mReferenceDrawable = p.drawable;
@@ -264,7 +271,7 @@
         }
     }
 
-    void updateItemDrawingParams(boolean animate) {
+    void updatePreviewItems(boolean animate) {
         buildParamsForPage(0, mFirstPageParams, animate);
     }
 
@@ -308,8 +315,8 @@
             int prevIndex = newParams.indexOf(moveIn.get(i));
             PreviewItemDrawingParams p = params.get(prevIndex);
             computePreviewItemDrawingParams(prevIndex, numItems, p);
-            updateTransitionParam(p, moveIn.get(i), mIcon.mPreviewLayoutRule.getEnterIndex(),
-                    newParams.indexOf(moveIn.get(i)));
+            updateTransitionParam(p, moveIn.get(i), ENTER_INDEX, newParams.indexOf(moveIn.get(i)),
+                    numItems);
         }
 
         // Items that are moving into new positions within the preview.
@@ -317,7 +324,7 @@
             int oldIndex = oldParams.indexOf(newParams.get(newIndex));
             if (oldIndex >= 0 && newIndex != oldIndex) {
                 PreviewItemDrawingParams p = params.get(newIndex);
-                updateTransitionParam(p, newParams.get(newIndex), oldIndex, newIndex);
+                updateTransitionParam(p, newParams.get(newIndex), oldIndex, newIndex, numItems);
             }
         }
 
@@ -328,7 +335,7 @@
             BubbleTextView item = moveOut.get(i);
             int oldIndex = oldParams.indexOf(item);
             PreviewItemDrawingParams p = computePreviewItemDrawingParams(oldIndex, numItems, null);
-            updateTransitionParam(p, item, oldIndex, mIcon.mPreviewLayoutRule.getExitIndex());
+            updateTransitionParam(p, item, oldIndex, EXIT_INDEX, numItems);
             params.add(0, p); // We want these items first so that they are on drawn last.
         }
 
@@ -340,7 +347,7 @@
     }
 
     private void updateTransitionParam(final PreviewItemDrawingParams p, BubbleTextView btv,
-            int prevIndex, int newIndex) {
+            int prevIndex, int newIndex, int numItems) {
         p.drawable = btv.getCompoundDrawables()[1];
         if (!mIcon.mFolder.isOpen()) {
             // Set the callback to FolderIcon as it is responsible to drawing the icon. The
@@ -348,9 +355,8 @@
             p.drawable.setCallback(mIcon);
         }
 
-        FolderPreviewItemAnim anim = new FolderPreviewItemAnim(this, p, prevIndex,
-                FolderIcon.NUM_ITEMS_IN_PREVIEW, newIndex, FolderIcon.NUM_ITEMS_IN_PREVIEW,
-                DROP_IN_ANIMATION_DURATION, null);
+        FolderPreviewItemAnim anim = new FolderPreviewItemAnim(this, p, prevIndex, numItems,
+                newIndex, numItems, DROP_IN_ANIMATION_DURATION, null);
         if (p.anim != null && !p.anim.hasEqualFinalState(anim)) {
             p.anim.cancel();
         }
diff --git a/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java b/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java
deleted file mode 100644
index 7d10556..0000000
--- a/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/**
- * Copyright (C) 2015 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.folder;
-
-public class StackFolderIconLayoutRule implements FolderIcon.PreviewLayoutRule {
-
-    static final int MAX_NUM_ITEMS_IN_PREVIEW = 3;
-
-    // The degree to which the item in the back of the stack is scaled [0...1]
-    // (0 means it's not scaled at all, 1 means it's scaled to nothing)
-    private static final float PERSPECTIVE_SCALE_FACTOR = 0.35f;
-
-    // The amount of vertical spread between items in the stack [0...1]
-    private static final float PERSPECTIVE_SHIFT_FACTOR = 0.18f;
-
-    private float mBaselineIconScale;
-    private int mBaselineIconSize;
-    private int mAvailableSpaceInPreview;
-    private float mMaxPerspectiveShift;
-
-    @Override
-    public void init(int availableSpace, float intrinsicIconSize, boolean rtl) {
-        mAvailableSpaceInPreview = availableSpace;
-
-        // cos(45) = 0.707  + ~= 0.1) = 0.8f
-        int adjustedAvailableSpace = (int) ((mAvailableSpaceInPreview / 2) * (1 + 0.8f));
-
-        int unscaledHeight = (int) (intrinsicIconSize * (1 + PERSPECTIVE_SHIFT_FACTOR));
-
-        mBaselineIconScale = (1.0f * adjustedAvailableSpace / unscaledHeight);
-
-        mBaselineIconSize = (int) (intrinsicIconSize * mBaselineIconScale);
-        mMaxPerspectiveShift = mBaselineIconSize * PERSPECTIVE_SHIFT_FACTOR;
-    }
-
-    @Override
-    public PreviewItemDrawingParams computePreviewItemDrawingParams(int index, int curNumItems,
-            PreviewItemDrawingParams params) {
-        float scale = scaleForItem(index, curNumItems);
-
-        index = MAX_NUM_ITEMS_IN_PREVIEW - index - 1;
-        float r = (index * 1.0f) / (MAX_NUM_ITEMS_IN_PREVIEW - 1);
-
-        float offset = (1 - r) * mMaxPerspectiveShift;
-        float scaledSize = scale * mBaselineIconSize;
-        float scaleOffsetCorrection = (1 - scale) * mBaselineIconSize;
-
-        // We want to imagine our coordinates from the bottom left, growing up and to the
-        // right. This is natural for the x-axis, but for the y-axis, we have to invert things.
-        float transY = mAvailableSpaceInPreview - (offset + scaledSize + scaleOffsetCorrection);
-        float transX = (mAvailableSpaceInPreview - scaledSize) / 2;
-        float totalScale = mBaselineIconScale * scale;
-        final float overlayAlpha = (80 * (1 - r)) / 255f;
-
-        if (params == null) {
-            params = new PreviewItemDrawingParams(transX, transY, totalScale, overlayAlpha);
-        } else {
-            params.update(transX, transY, totalScale);
-            params.overlayAlpha = overlayAlpha;
-        }
-        return params;
-    }
-
-    @Override
-    public int maxNumItems() {
-        return MAX_NUM_ITEMS_IN_PREVIEW;
-    }
-
-    @Override
-    public float getIconSize() {
-        return mBaselineIconSize;
-    }
-
-    @Override
-    public float scaleForItem(int index, int numItems) {
-        // Scale is determined by the position of the icon in the preview.
-        index = MAX_NUM_ITEMS_IN_PREVIEW - index - 1;
-        float r = (index * 1.0f) / (MAX_NUM_ITEMS_IN_PREVIEW - 1);
-        return (1 - PERSPECTIVE_SCALE_FACTOR * (1 - r));
-    }
-
-    @Override
-    public boolean clipToBackground() {
-        return false;
-    }
-
-    @Override
-    public boolean hasEnterExitIndices() {
-        return false;
-    }
-
-    @Override
-    public int getExitIndex() {
-        throw new RuntimeException("hasEnterExitIndices not supported");
-    }
-
-    @Override
-    public int getEnterIndex() {
-        throw new RuntimeException("hasEnterExitIndices not supported");
-    }
-}
diff --git a/src/com/android/launcher3/graphics/DragPreviewProvider.java b/src/com/android/launcher3/graphics/DragPreviewProvider.java
index 10e91c0..355c231 100644
--- a/src/com/android/launcher3/graphics/DragPreviewProvider.java
+++ b/src/com/android/launcher3/graphics/DragPreviewProvider.java
@@ -18,10 +18,15 @@
 
 import android.content.Context;
 import android.graphics.Bitmap;
+import android.graphics.BlurMaskFilter;
 import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
 import android.graphics.Rect;
 import android.graphics.Region.Op;
 import android.graphics.drawable.Drawable;
+import android.os.Handler;
 import android.view.View;
 
 import com.android.launcher3.BubbleTextView;
@@ -30,6 +35,9 @@
 import com.android.launcher3.R;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.util.UiThreadHelper;
+
+import java.nio.ByteBuffer;
 
 /**
  * A utility class to generate preview bitmap for dragging.
@@ -45,6 +53,7 @@
 
     protected final int blurSizeOutline;
 
+    private OutlineGeneratorCallback mOutlineGeneratorCallback;
     public Bitmap generatedDragOutline;
 
     public DragPreviewProvider(View view) {
@@ -106,7 +115,7 @@
      * Returns a new bitmap to show when the {@link #mView} is being dragged around.
      * Responsibility for the bitmap is transferred to the caller.
      */
-    public Bitmap createDragBitmap(Canvas canvas) {
+    public Bitmap createDragBitmap() {
         float scale = 1f;
         int width = mView.getWidth();
         int height = mView.getHeight();
@@ -124,7 +133,7 @@
 
         Bitmap b = Bitmap.createBitmap(width + blurSizeOutline, height + blurSizeOutline,
                 Bitmap.Config.ARGB_8888);
-        canvas.setBitmap(b);
+        Canvas canvas = new Canvas(b);
 
         canvas.save();
         canvas.scale(scale, scale);
@@ -136,43 +145,13 @@
         return b;
     }
 
-    public final void generateDragOutline(Canvas canvas) {
-        if (FeatureFlags.IS_DOGFOOD_BUILD && generatedDragOutline != null) {
+    public final void generateDragOutline(Bitmap preview) {
+        if (FeatureFlags.IS_DOGFOOD_BUILD && mOutlineGeneratorCallback != null) {
             throw new RuntimeException("Drag outline generated twice");
         }
 
-        generatedDragOutline = createDragOutline(canvas);
-    }
-
-    /**
-     * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location.
-     * Responsibility for the bitmap is transferred to the caller.
-     */
-    public Bitmap createDragOutline(Canvas canvas) {
-        float scale = 1f;
-        int width = mView.getWidth();
-        int height = mView.getHeight();
-
-        if (mView instanceof LauncherAppWidgetHostView) {
-            scale = ((LauncherAppWidgetHostView) mView).getScaleToFit();
-            width = (int) Math.floor(mView.getWidth() * scale);
-            height = (int) Math.floor(mView.getHeight() * scale);
-        }
-
-        Bitmap b = Bitmap.createBitmap(width + blurSizeOutline, height + blurSizeOutline,
-                Bitmap.Config.ALPHA_8);
-        canvas.setBitmap(b);
-
-        canvas.save();
-        canvas.scale(scale, scale);
-        drawDragView(canvas);
-        canvas.restore();
-
-        HolographicOutlineHelper.getInstance(mView.getContext())
-                .applyExpensiveOutlineWithBlur(b, canvas);
-
-        canvas.setBitmap(null);
-        return b;
+        mOutlineGeneratorCallback = new OutlineGeneratorCallback(preview);
+        new Handler(UiThreadHelper.getBackgroundLooper()).post(mOutlineGeneratorCallback);
     }
 
     protected static Rect getDrawableBounds(Drawable d) {
@@ -201,4 +180,89 @@
                 - previewPadding / 2);
         return scale;
     }
+
+    protected Bitmap convertPreviewToAlphaBitmap(Bitmap preview) {
+        return preview.copy(Bitmap.Config.ALPHA_8, true);
+    }
+
+    private class OutlineGeneratorCallback implements Runnable {
+
+        private final Bitmap mPreviewSnapshot;
+        private final Context mContext;
+
+        OutlineGeneratorCallback(Bitmap preview) {
+            mPreviewSnapshot = preview;
+            mContext = mView.getContext();
+        }
+
+        @Override
+        public void run() {
+            Bitmap preview = convertPreviewToAlphaBitmap(mPreviewSnapshot);
+
+            // We start by removing most of the alpha channel so as to ignore shadows, and
+            // other types of partial transparency when defining the shape of the object
+            byte[] pixels = new byte[preview.getWidth() * preview.getHeight()];
+            ByteBuffer buffer = ByteBuffer.wrap(pixels);
+            buffer.rewind();
+            preview.copyPixelsToBuffer(buffer);
+
+            for (int i = 0; i < pixels.length; i++) {
+                if ((pixels[i] & 0xFF) < 188) {
+                    pixels[i] = 0;
+                }
+            }
+
+            buffer.rewind();
+            preview.copyPixelsFromBuffer(buffer);
+
+            final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
+            Canvas canvas = new Canvas();
+
+            // calculate the outer blur first
+            paint.setMaskFilter(new BlurMaskFilter(blurSizeOutline, BlurMaskFilter.Blur.OUTER));
+            int[] outerBlurOffset = new int[2];
+            Bitmap thickOuterBlur = preview.extractAlpha(paint, outerBlurOffset);
+
+            paint.setMaskFilter(new BlurMaskFilter(
+                    mContext.getResources().getDimension(R.dimen.blur_size_thin_outline),
+                    BlurMaskFilter.Blur.OUTER));
+            int[] brightOutlineOffset = new int[2];
+            Bitmap brightOutline = preview.extractAlpha(paint, brightOutlineOffset);
+
+            // calculate the inner blur
+            canvas.setBitmap(preview);
+            canvas.drawColor(0xFF000000, PorterDuff.Mode.SRC_OUT);
+            paint.setMaskFilter(new BlurMaskFilter(blurSizeOutline, BlurMaskFilter.Blur.NORMAL));
+            int[] thickInnerBlurOffset = new int[2];
+            Bitmap thickInnerBlur = preview.extractAlpha(paint, thickInnerBlurOffset);
+
+            // mask out the inner blur
+            paint.setMaskFilter(null);
+            paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
+            canvas.setBitmap(thickInnerBlur);
+            canvas.drawBitmap(preview, -thickInnerBlurOffset[0],
+                    -thickInnerBlurOffset[1], paint);
+            canvas.drawRect(0, 0, -thickInnerBlurOffset[0], thickInnerBlur.getHeight(), paint);
+            canvas.drawRect(0, 0, thickInnerBlur.getWidth(), -thickInnerBlurOffset[1], paint);
+
+            // draw the inner and outer blur
+            paint.setXfermode(null);
+            canvas.setBitmap(preview);
+            canvas.drawColor(0, PorterDuff.Mode.CLEAR);
+            canvas.drawBitmap(thickInnerBlur, thickInnerBlurOffset[0], thickInnerBlurOffset[1],
+                    paint);
+            canvas.drawBitmap(thickOuterBlur, outerBlurOffset[0], outerBlurOffset[1], paint);
+
+            // draw the bright outline
+            canvas.drawBitmap(brightOutline, brightOutlineOffset[0], brightOutlineOffset[1], paint);
+
+            // cleanup
+            canvas.setBitmap(null);
+            brightOutline.recycle();
+            thickOuterBlur.recycle();
+            thickInnerBlur.recycle();
+
+            generatedDragOutline = preview;
+        }
+    }
 }
diff --git a/src/com/android/launcher3/graphics/GradientView.java b/src/com/android/launcher3/graphics/GradientView.java
index 5455b43..bacb063 100644
--- a/src/com/android/launcher3/graphics/GradientView.java
+++ b/src/com/android/launcher3/graphics/GradientView.java
@@ -29,12 +29,12 @@
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
 import android.view.View;
-import android.view.animation.AccelerateInterpolator;
 import android.view.animation.Interpolator;
 
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.dynamicui.WallpaperColorInfo;
 import com.android.launcher3.util.Themes;
 
@@ -64,7 +64,7 @@
     private final int mMaskHeight, mMaskWidth;
     private final int mAlphaColors;
     private final Paint mDebugPaint = DEBUG ? new Paint() : null;
-    private final Interpolator mAccelerator = new AccelerateInterpolator();
+    private final Interpolator mAccelerator = Interpolators.ACCEL;
     private final float mAlphaStart;
     private final WallpaperColorInfo mWallpaperColorInfo;
     private final int mScrimColor;
diff --git a/src/com/android/launcher3/graphics/HolographicOutlineHelper.java b/src/com/android/launcher3/graphics/HolographicOutlineHelper.java
index b221828..fdf2d67 100644
--- a/src/com/android/launcher3/graphics/HolographicOutlineHelper.java
+++ b/src/com/android/launcher3/graphics/HolographicOutlineHelper.java
@@ -16,8 +16,9 @@
 
 package com.android.launcher3.graphics;
 
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_ADAPTIVE_ICON;
+
 import android.content.Context;
-import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.BlurMaskFilter;
 import android.graphics.Canvas;
@@ -30,10 +31,8 @@
 import android.util.SparseArray;
 
 import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.ItemInfoWithIcon;
 import com.android.launcher3.R;
-import com.android.launcher3.config.FeatureFlags;
-
-import java.nio.ByteBuffer;
 
 /**
  * Utility class to generate shadow and outline effect, which are used for click feedback
@@ -41,17 +40,18 @@
  */
 public class HolographicOutlineHelper {
 
+    /**
+     * Bitmap used as shadow for Adaptive icons
+     */
+    public static final Bitmap ADAPTIVE_ICON_SHADOW_BITMAP =
+            Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8);
+
     private static HolographicOutlineHelper sInstance;
 
     private final Canvas mCanvas = new Canvas();
-    private final Paint mDrawPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
     private final Paint mBlurPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
     private final Paint mErasePaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
 
-    private final BlurMaskFilter mMediumOuterBlurMaskFilter;
-    private final BlurMaskFilter mThinOuterBlurMaskFilter;
-    private final BlurMaskFilter mMediumInnerBlurMaskFilter;
-
     private final float mShadowBitmapShift;
     private final BlurMaskFilter mShadowBlurMaskFilter;
 
@@ -59,18 +59,8 @@
     private final SparseArray<Bitmap> mBitmapCache = new SparseArray<>(4);
 
     private HolographicOutlineHelper(Context context) {
-        Resources res = context.getResources();
-
-        float mediumBlur = res.getDimension(R.dimen.blur_size_medium_outline);
-        mMediumOuterBlurMaskFilter = new BlurMaskFilter(mediumBlur, BlurMaskFilter.Blur.OUTER);
-        mMediumInnerBlurMaskFilter = new BlurMaskFilter(mediumBlur, BlurMaskFilter.Blur.NORMAL);
-
-        mThinOuterBlurMaskFilter = new BlurMaskFilter(
-                res.getDimension(R.dimen.blur_size_thin_outline), BlurMaskFilter.Blur.OUTER);
-
-        mShadowBitmapShift = res.getDimension(R.dimen.blur_size_click_shadow);
+        mShadowBitmapShift = context.getResources().getDimension(R.dimen.blur_size_click_shadow);
         mShadowBlurMaskFilter = new BlurMaskFilter(mShadowBitmapShift, BlurMaskFilter.Blur.NORMAL);
-
         mErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
     }
 
@@ -81,76 +71,12 @@
         return sInstance;
     }
 
-    /**
-     * Applies a more expensive and accurate outline to whatever is currently drawn in a specified
-     * bitmap.
-     */
-    public void applyExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas) {
-        if (FeatureFlags.IS_DOGFOOD_BUILD && srcDst.getConfig() != Bitmap.Config.ALPHA_8) {
-            throw new RuntimeException("Outline blue is only supported on alpha bitmaps");
-        }
-
-        // We start by removing most of the alpha channel so as to ignore shadows, and
-        // other types of partial transparency when defining the shape of the object
-        byte[] pixels = new byte[srcDst.getWidth() * srcDst.getHeight()];
-        ByteBuffer buffer = ByteBuffer.wrap(pixels);
-        buffer.rewind();
-        srcDst.copyPixelsToBuffer(buffer);
-
-        for (int i = 0; i < pixels.length; i++) {
-            if ((pixels[i] & 0xFF) < 188) {
-                pixels[i] = 0;
-            }
-        }
-
-        buffer.rewind();
-        srcDst.copyPixelsFromBuffer(buffer);
-
-        // calculate the outer blur first
-        mBlurPaint.setMaskFilter(mMediumOuterBlurMaskFilter);
-        int[] outerBlurOffset = new int[2];
-        Bitmap thickOuterBlur = srcDst.extractAlpha(mBlurPaint, outerBlurOffset);
-
-        mBlurPaint.setMaskFilter(mThinOuterBlurMaskFilter);
-        int[] brightOutlineOffset = new int[2];
-        Bitmap brightOutline = srcDst.extractAlpha(mBlurPaint, brightOutlineOffset);
-
-        // calculate the inner blur
-        srcDstCanvas.setBitmap(srcDst);
-        srcDstCanvas.drawColor(0xFF000000, PorterDuff.Mode.SRC_OUT);
-        mBlurPaint.setMaskFilter(mMediumInnerBlurMaskFilter);
-        int[] thickInnerBlurOffset = new int[2];
-        Bitmap thickInnerBlur = srcDst.extractAlpha(mBlurPaint, thickInnerBlurOffset);
-
-        // mask out the inner blur
-        srcDstCanvas.setBitmap(thickInnerBlur);
-        srcDstCanvas.drawBitmap(srcDst, -thickInnerBlurOffset[0],
-                -thickInnerBlurOffset[1], mErasePaint);
-        srcDstCanvas.drawRect(0, 0, -thickInnerBlurOffset[0], thickInnerBlur.getHeight(),
-                mErasePaint);
-        srcDstCanvas.drawRect(0, 0, thickInnerBlur.getWidth(), -thickInnerBlurOffset[1],
-                mErasePaint);
-
-        // draw the inner and outer blur
-        srcDstCanvas.setBitmap(srcDst);
-        srcDstCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
-        srcDstCanvas.drawBitmap(thickInnerBlur, thickInnerBlurOffset[0], thickInnerBlurOffset[1],
-                mDrawPaint);
-        srcDstCanvas.drawBitmap(thickOuterBlur, outerBlurOffset[0], outerBlurOffset[1],
-                mDrawPaint);
-
-        // draw the bright outline
-        srcDstCanvas.drawBitmap(brightOutline, brightOutlineOffset[0], brightOutlineOffset[1],
-                mDrawPaint);
-
-        // cleanup
-        srcDstCanvas.setBitmap(null);
-        brightOutline.recycle();
-        thickOuterBlur.recycle();
-        thickInnerBlur.recycle();
-    }
-
     public Bitmap createMediumDropShadow(BubbleTextView view) {
+        if (view.getTag() instanceof ItemInfoWithIcon &&
+                ((((ItemInfoWithIcon) view.getTag()).runtimeStatusFlags & FLAG_ADAPTIVE_ICON)
+                        != 0)) {
+            return ADAPTIVE_ICON_SHADOW_BITMAP;
+        }
         Drawable drawable = view.getIcon();
         if (drawable == null) {
             return null;
@@ -207,7 +133,7 @@
     }
 
     public void recycleShadowBitmap(Bitmap bitmap) {
-        if (bitmap != null) {
+        if (bitmap != null && bitmap != ADAPTIVE_ICON_SHADOW_BITMAP) {
             mBitmapCache.put((bitmap.getWidth() << 16) | bitmap.getHeight(), bitmap);
         }
     }
diff --git a/src/com/android/launcher3/graphics/IconNormalizer.java b/src/com/android/launcher3/graphics/IconNormalizer.java
index 28fc423..5ee6a30 100644
--- a/src/com/android/launcher3/graphics/IconNormalizer.java
+++ b/src/com/android/launcher3/graphics/IconNormalizer.java
@@ -28,12 +28,15 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.util.Log;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
+
 import java.io.File;
 import java.io.FileOutputStream;
 import java.nio.ByteBuffer;
@@ -231,12 +234,17 @@
      */
     public synchronized float getScale(@NonNull Drawable d, @Nullable RectF outBounds,
             @Nullable Path path, @Nullable boolean[] outMaskShape) {
-        if (Utilities.ATLEAST_OREO && d instanceof AdaptiveIconDrawable &&
-                mAdaptiveIconScale != SCALE_NOT_INITIALIZED) {
-            if (outBounds != null) {
-                outBounds.set(mAdaptiveIconBounds);
+        if (Utilities.ATLEAST_OREO && d instanceof AdaptiveIconDrawable) {
+            if (mAdaptiveIconScale != SCALE_NOT_INITIALIZED) {
+                if (outBounds != null) {
+                    outBounds.set(mAdaptiveIconBounds);
+                }
+                return mAdaptiveIconScale;
             }
-            return mAdaptiveIconScale;
+            if (d instanceof FolderAdaptiveIcon) {
+                // Since we just want the scale, avoid heavy drawing operations
+                d = new AdaptiveIconDrawable(new ColorDrawable(Color.BLACK), null);
+            }
         }
         int width = d.getIntrinsicWidth();
         int height = d.getIntrinsicHeight();
diff --git a/src/com/android/launcher3/graphics/LauncherIcons.java b/src/com/android/launcher3/graphics/LauncherIcons.java
index d55baf0..1eddb2c 100644
--- a/src/com/android/launcher3/graphics/LauncherIcons.java
+++ b/src/com/android/launcher3/graphics/LauncherIcons.java
@@ -16,10 +16,12 @@
 
 package com.android.launcher3.graphics;
 
+import android.annotation.TargetApi;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.Intent.ShortcutIconResource;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
@@ -73,8 +75,12 @@
             Resources resources = packageManager.getResourcesForApplication(iconRes.packageName);
             if (resources != null) {
                 final int id = resources.getIdentifier(iconRes.resourceName, null, null);
-                return createIconBitmap(resources.getDrawableForDensity(
-                        id, LauncherAppState.getIDP(context).fillResIconDpi), context);
+                // do not stamp old legacy shortcuts as the app may have already forgotten about it
+                return createBadgedIconBitmap(resources.getDrawableForDensity(
+                        id, LauncherAppState.getIDP(context).fillResIconDpi),
+                        Process.myUserHandle() /* only available on primary user */,
+                        context,
+                        0 /* do not apply legacy treatment */);
             }
         } catch (Exception e) {
             // Icon not found.
@@ -90,11 +96,12 @@
         if (iconBitmapSize == icon.getWidth() && iconBitmapSize == icon.getHeight()) {
             return icon;
         }
-        return createIconBitmap(new BitmapDrawable(context.getResources(), icon), context);
+        return createIconBitmap(new BitmapDrawable(context.getResources(), icon), context, 1f);
     }
 
     /**
-     * Returns a bitmap suitable for the all apps view. The icon is badged for {@param user}.
+     * Returns a bitmap suitable for displaying as an icon at various launcher UIs like all apps
+     * view or workspace. The icon is badged for {@param user}.
      * The bitmap is also visually normalized with other icons.
      */
     public static Bitmap createBadgedIconBitmap(
@@ -127,24 +134,18 @@
                 icon instanceof AdaptiveIconDrawable) {
             bitmap = ShadowGenerator.getInstance(context).recreateIcon(bitmap);
         }
-        return badgeIconForUser(bitmap, user, context);
-    }
 
-    /**
-     * Badges the provided icon with the user badge if required.
-     */
-    public static Bitmap badgeIconForUser(Bitmap icon, UserHandle user, Context context) {
         if (user != null && !Process.myUserHandle().equals(user)) {
-            BitmapDrawable drawable = new FixedSizeBitmapDrawable(icon);
+            BitmapDrawable drawable = new FixedSizeBitmapDrawable(bitmap);
             Drawable badged = context.getPackageManager().getUserBadgedIcon(
                     drawable, user);
             if (badged instanceof BitmapDrawable) {
                 return ((BitmapDrawable) badged).getBitmap();
             } else {
-                return createIconBitmap(badged, context);
+                return createIconBitmap(badged, context, 1f);
             }
         } else {
-            return icon;
+            return bitmap;
         }
     }
 
@@ -152,7 +153,8 @@
      * Creates a normalized bitmap suitable for the all apps view. The bitmap is also visually
      * normalized with other icons and has enough spacing to add shadow.
      */
-    public static Bitmap createScaledBitmapWithoutShadow(Drawable icon, Context context, int iconAppTargetSdk) {
+    public static Bitmap createScaledBitmapWithoutShadow(
+            Drawable icon, Context context, int iconAppTargetSdk) {
         RectF iconBounds = new RectF();
         IconNormalizer normalizer;
         float scale = 1f;
@@ -181,21 +183,6 @@
         return createIconBitmap(icon, context, scale);
     }
 
-    /**
-     * Adds a shadow to the provided icon. It assumes that the icon has already been scaled using
-     * {@link #createScaledBitmapWithoutShadow(Drawable, Context, int)}
-     */
-    public static Bitmap addShadowToIcon(Bitmap icon, Context context) {
-        return ShadowGenerator.getInstance(context).recreateIcon(icon);
-    }
-
-    /**
-     * Adds the {@param badge} on top of {@param srcTgt} using the badge dimensions.
-     */
-    public static Bitmap badgeWithBitmap(Bitmap srcTgt, Bitmap badge, Context context) {
-        return badgeWithDrawable(srcTgt, new FastBitmapDrawable(badge), context);
-    }
-
     public static Bitmap badgeWithDrawable(Bitmap srcTgt, Drawable badge, Context context) {
         int badgeSize = context.getResources().getDimensionPixelSize(R.dimen.profile_badge_size);
         synchronized (sCanvas) {
@@ -209,26 +196,9 @@
     }
 
     /**
-     * Returns a bitmap suitable for the all apps view.
-     */
-    public static Bitmap createIconBitmap(Drawable icon, Context context) {
-        float scale = 1f;
-        if (FeatureFlags.ADAPTIVE_ICON_SHADOW && Utilities.ATLEAST_OREO &&
-                icon instanceof AdaptiveIconDrawable) {
-            scale = ShadowGenerator.getScaleForBounds(new RectF(0, 0, 0, 0));
-        }
-        Bitmap bitmap =  createIconBitmap(icon, context, scale);
-        if (FeatureFlags.ADAPTIVE_ICON_SHADOW && Utilities.ATLEAST_OREO &&
-                icon instanceof AdaptiveIconDrawable) {
-            bitmap = ShadowGenerator.getInstance(context).recreateIcon(bitmap);
-        }
-        return bitmap;
-    }
-
-    /**
      * @param scale the scale to apply before drawing {@param icon} on the canvas
      */
-    public static Bitmap createIconBitmap(Drawable icon, Context context, float scale) {
+    private static Bitmap createIconBitmap(Drawable icon, Context context, float scale) {
         synchronized (sCanvas) {
             final int iconBitmapSize = LauncherAppState.getIDP(context).iconBitmapSize;
             int width = iconBitmapSize;
@@ -295,7 +265,9 @@
      * shrink the legacy icon and set it as foreground. Use color drawable as background to
      * create AdaptiveIconDrawable.
      */
-    static Drawable wrapToAdaptiveIconDrawable(Context context, Drawable drawable, float scale) {
+    @TargetApi(Build.VERSION_CODES.O)
+    private static Drawable wrapToAdaptiveIconDrawable(
+            Context context, Drawable drawable, float scale) {
         if (!(FeatureFlags.LEGACY_ICON_TREATMENT && Utilities.ATLEAST_OREO)) {
             return drawable;
         }
@@ -307,7 +279,7 @@
                 FixedScaleDrawable fsd = ((FixedScaleDrawable) iconWrapper.getForeground());
                 fsd.setDrawable(drawable);
                 fsd.setScale(scale);
-                return (Drawable) iconWrapper;
+                return iconWrapper;
             }
         } catch (Exception e) {
             return drawable;
@@ -326,15 +298,9 @@
 
     public static Bitmap createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context,
             final Bitmap fallbackIcon) {
-        Provider<Bitmap> fallbackIconProvider = new Provider<Bitmap>() {
-            @Override
-            public Bitmap get() {
-                // If the shortcut is pinned but no longer has an icon in the system,
-                // keep the current icon instead of reverting to the default icon.
-                return fallbackIcon;
-            }
-        };
-        return createShortcutIcon(shortcutInfo, context, true, fallbackIconProvider);
+        // If the shortcut is pinned but no longer has an icon in the system,
+        // keep the current icon instead of reverting to the default icon.
+        return createShortcutIcon(shortcutInfo, context, true, Provider.of(fallbackIcon));
     }
 
     public static Bitmap createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context,
@@ -360,8 +326,9 @@
         if (!badged) {
             return unbadgedBitmap;
         }
-        unbadgedBitmap = LauncherIcons.addShadowToIcon(unbadgedBitmap, context);
-        return badgeWithBitmap(unbadgedBitmap, getShortcutInfoBadge(shortcutInfo, cache), context);
+        unbadgedBitmap = ShadowGenerator.getInstance(context).recreateIcon(unbadgedBitmap);
+        return badgeWithDrawable(unbadgedBitmap,
+                new FastBitmapDrawable(getShortcutInfoBadge(shortcutInfo, cache)), context);
     }
 
     public static Bitmap getShortcutInfoBadge(ShortcutInfoCompat shortcutInfo, IconCache cache) {
diff --git a/src/com/android/launcher3/graphics/PreloadIconDrawable.java b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
index 06dc7ac..6d486ee 100644
--- a/src/com/android/launcher3/graphics/PreloadIconDrawable.java
+++ b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
@@ -30,9 +30,9 @@
 import android.graphics.Rect;
 import android.util.Property;
 import android.util.SparseArray;
-import android.view.animation.LinearInterpolator;
 
 import com.android.launcher3.FastBitmapDrawable;
+import com.android.launcher3.anim.Interpolators;
 
 import java.lang.ref.WeakReference;
 
@@ -226,7 +226,7 @@
             mCurrentAnim = ObjectAnimator.ofFloat(this, INTERNAL_STATE, finalProgress);
             mCurrentAnim.setDuration(
                     (long) ((finalProgress - mInternalStateProgress) * DURATION_SCALE));
-            mCurrentAnim.setInterpolator(new LinearInterpolator());
+            mCurrentAnim.setInterpolator(Interpolators.LINEAR);
             if (isFinish) {
                 mCurrentAnim.addListener(new AnimatorListenerAdapter() {
                     @Override
@@ -237,7 +237,6 @@
             }
             mCurrentAnim.start();
         }
-
     }
 
     /**
diff --git a/src/com/android/launcher3/logging/LoggerUtils.java b/src/com/android/launcher3/logging/LoggerUtils.java
index 81333b1..00ee009 100644
--- a/src/com/android/launcher3/logging/LoggerUtils.java
+++ b/src/com/android/launcher3/logging/LoggerUtils.java
@@ -91,7 +91,8 @@
                 return getFieldName(t.controlType, ControlType.class);
             case Target.Type.CONTAINER:
                 String str = getFieldName(t.containerType, ContainerType.class);
-                if (t.containerType == ContainerType.WORKSPACE) {
+                if (t.containerType == ContainerType.WORKSPACE ||
+                        t.containerType == ContainerType.HOTSEAT) {
                     str += " id=" + t.pageIndex;
                 } else if (t.containerType == ContainerType.FOLDER) {
                     str += " grid(" + t.gridX + "," + t.gridY+ ")";
@@ -105,13 +106,16 @@
     private static String getItemStr(Target t) {
         String typeStr = getFieldName(t.itemType, ItemType.class);
         if (t.packageNameHash != 0) {
-            typeStr += ", packageHash=" + t.packageNameHash + ", predictiveRank=" + t.predictedRank;
+            typeStr += ", packageHash=" + t.packageNameHash;
         }
         if (t.componentHash != 0) {
-            typeStr += ", componentHash=" + t.componentHash + ", predictiveRank=" + t.predictedRank;
+            typeStr += ", componentHash=" + t.componentHash;
         }
         if (t.intentHash != 0) {
-            typeStr += ", intentHash=" + t.intentHash + ", predictiveRank=" + t.predictedRank;
+            typeStr += ", intentHash=" + t.intentHash;
+        }
+        if (t.packageNameHash != 0 || t.componentHash != 0 || t.intentHash != 0) {
+            typeStr += ", predictiveRank=" + t.predictedRank;
         }
         return typeStr + ", grid(" + t.gridX + "," + t.gridY + "), span(" + t.spanX + "," + t.spanY
                 + "), pageIdx=" + t.pageIndex;
diff --git a/src/com/android/launcher3/logging/UserEventDispatcher.java b/src/com/android/launcher3/logging/UserEventDispatcher.java
index d5c6515..243dbea 100644
--- a/src/com/android/launcher3/logging/UserEventDispatcher.java
+++ b/src/com/android/launcher3/logging/UserEventDispatcher.java
@@ -177,13 +177,11 @@
     }
 
     public void logActionCommand(int command, int containerType) {
-        logActionCommand(command, containerType, 0);
+        logActionCommand(command, newContainerTarget(containerType));
     }
 
-    public void logActionCommand(int command, int containerType, int pageIndex) {
-        LauncherEvent event = newLauncherEvent(
-                newCommandAction(command), newContainerTarget(containerType));
-        event.srcTarget[0].pageIndex = pageIndex;
+    public void logActionCommand(int command, Target target) {
+        LauncherEvent event = newLauncherEvent(newCommandAction(command), target);
         dispatchUserEvent(event, null);
     }
 
@@ -234,6 +232,10 @@
         event.action.dir = dir;
         event.srcTarget[0].pageIndex = pageIndex;
         dispatchUserEvent(event, null);
+
+        if (action == Action.Touch.SWIPE) {
+            resetElapsedContainerMillis();
+        }
     }
 
     public void logActionOnItem(int action, int dir, int itemType) {
@@ -325,6 +327,7 @@
                 ev.actionDurationMillis);
         log += "\n isInLandscapeMode " + ev.isInLandscapeMode;
         log += "\n isInMultiWindowMode " + ev.isInMultiWindowMode;
+        log += "\n";
         Log.d(TAG, log);
     }
 
diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
index 42926fa..a33a039 100644
--- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
+++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
@@ -38,7 +38,6 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.util.GridOccupancy;
 import com.android.launcher3.util.ManagedProfileHeuristic.UserFolderInfo;
-import com.android.launcher3.util.Provider;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -47,19 +46,18 @@
  */
 public class AddWorkspaceItemsTask extends BaseModelUpdateTask {
 
-    private final Provider<List<Pair<ItemInfo, Object>>> mAppsProvider;
+    private final List<Pair<ItemInfo, Object>> mItemList;
 
     /**
-     * @param appsProvider items to add on the workspace
+     * @param itemList items to add on the workspace
      */
-    public AddWorkspaceItemsTask(Provider<List<Pair<ItemInfo, Object>>> appsProvider) {
-        mAppsProvider = appsProvider;
+    public AddWorkspaceItemsTask(List<Pair<ItemInfo, Object>> itemList) {
+        mItemList = itemList;
     }
 
     @Override
     public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
-        List<Pair<ItemInfo, Object>> workspaceApps = mAppsProvider.get();
-        if (workspaceApps.isEmpty()) {
+        if (mItemList.isEmpty()) {
             return;
         }
         Context context = app.getContext();
@@ -75,7 +73,7 @@
         synchronized(dataModel) {
 
             List<ItemInfo> filteredItems = new ArrayList<>();
-            for (Pair<ItemInfo, Object> entry : workspaceApps) {
+            for (Pair<ItemInfo, Object> entry : mItemList) {
                 ItemInfo item = entry.first;
                 if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
                         item.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
diff --git a/src/com/android/launcher3/model/BaseModelUpdateTask.java b/src/com/android/launcher3/model/BaseModelUpdateTask.java
index d5b5aa7..9aa30e7 100644
--- a/src/com/android/launcher3/model/BaseModelUpdateTask.java
+++ b/src/com/android/launcher3/model/BaseModelUpdateTask.java
@@ -28,6 +28,8 @@
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.MultiHashMap;
+import com.android.launcher3.widget.WidgetListRowEntry;
+import com.android.launcher3.widget.WidgetsListAdapter;
 
 import java.util.ArrayList;
 import java.util.concurrent.Executor;
@@ -117,8 +119,8 @@
     }
 
     public void bindUpdatedWidgets(BgDataModel dataModel) {
-        final MultiHashMap<PackageItemInfo, WidgetItem> widgets
-                = dataModel.widgetsModel.getWidgetsMap();
+        final ArrayList<WidgetListRowEntry> widgets =
+                dataModel.widgetsModel.getWidgetsList(mApp.getContext());
         scheduleCallbackTask(new CallbackTask() {
             @Override
             public void execute(Callbacks callbacks) {
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTask.java b/src/com/android/launcher3/model/GridSizeMigrationTask.java
index 8de0de0..d9b1a3f 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTask.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTask.java
@@ -726,7 +726,7 @@
                                 mContext).getLauncherAppWidgetInfo(widgetId);
                         Point spans = null;
                         if (pInfo != null) {
-                            spans = pInfo.getMinSpans(mIdp, mContext);
+                            spans = pInfo.getMinSpans();
                         }
                         if (spans != null) {
                             entry.minSpanX = spans.x > 0 ? spans.x : entry.spanX;
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index bc7da9b..ccef9b7 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -32,6 +32,7 @@
 import android.util.Log;
 import android.util.LongSparseArray;
 
+import com.android.launcher3.AppInfo;
 import com.android.launcher3.IconCache;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.ItemInfo;
@@ -48,7 +49,6 @@
 import com.android.launcher3.util.ContentWriter;
 import com.android.launcher3.util.GridOccupancy;
 import com.android.launcher3.util.LongArrayMap;
-import com.android.launcher3.util.PackageManagerHelper;
 
 import java.net.URISyntaxException;
 import java.security.InvalidParameterException;
@@ -202,7 +202,6 @@
         return TextUtils.isEmpty(title) ? "" : Utilities.trim(title);
     }
 
-
     /**
      * Make an ShortcutInfo object for a restored application or shortcut item that points
      * to a package that is not yet installed on the system.
@@ -274,8 +273,8 @@
             info.iconBitmap = icon != null ? icon : info.iconBitmap;
         }
 
-        if (lai != null && PackageManagerHelper.isAppSuspended(lai.getApplicationInfo())) {
-            info.isDisabled = ShortcutInfo.FLAG_DISABLED_SUSPENDED;
+        if (lai != null) {
+            AppInfo.updateRuntimeFlagsForActivityTarget(info, lai);
         }
 
         // from the db
diff --git a/src/com/android/launcher3/model/LoaderResults.java b/src/com/android/launcher3/model/LoaderResults.java
index b7a6b68..24e5b9c 100644
--- a/src/com/android/launcher3/model/LoaderResults.java
+++ b/src/com/android/launcher3/model/LoaderResults.java
@@ -36,6 +36,8 @@
 import com.android.launcher3.util.LooperIdleLock;
 import com.android.launcher3.util.MultiHashMap;
 import com.android.launcher3.util.ViewOnDrawExecutor;
+import com.android.launcher3.widget.WidgetListRowEntry;
+import com.android.launcher3.widget.WidgetsListAdapter;
 
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
@@ -362,8 +364,8 @@
     }
 
     public void bindWidgets() {
-        final MultiHashMap<PackageItemInfo, WidgetItem> widgets
-                = mBgDataModel.widgetsModel.getWidgetsMap();
+        final ArrayList<WidgetListRowEntry> widgets =
+                mBgDataModel.widgetsModel.getWidgetsList(mApp.getContext());
         Runnable r = new Runnable() {
             public void run() {
                 Callbacks callbacks = mCallbacks.get();
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 4756edc..310416f 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -25,10 +25,9 @@
 import android.content.pm.LauncherActivityInfo;
 import android.content.pm.PackageInstaller;
 import android.graphics.Bitmap;
+import android.graphics.drawable.AdaptiveIconDrawable;
 import android.os.Handler;
 import android.os.Process;
-import android.os.SystemClock;
-import android.os.Trace;
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Log;
@@ -37,6 +36,7 @@
 
 import com.android.launcher3.AllAppsList;
 import com.android.launcher3.AppInfo;
+import com.android.launcher3.ClickShadowView;
 import com.android.launcher3.FolderInfo;
 import com.android.launcher3.IconCache;
 import com.android.launcher3.InstallShortcutReceiver;
@@ -53,8 +53,8 @@
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.folder.Folder;
-import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.folder.FolderIconPreviewVerifier;
+import com.android.launcher3.graphics.IconNormalizer;
 import com.android.launcher3.graphics.LauncherIcons;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.provider.ImportDataTask;
@@ -67,6 +67,7 @@
 import com.android.launcher3.util.MultiHashMap;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.Provider;
+import com.android.launcher3.util.TraceHelper;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -76,6 +77,11 @@
 import java.util.Map;
 import java.util.concurrent.CancellationException;
 
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER;
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SAFEMODE;
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SUSPENDED;
+import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
+
 /**
  * Runnable for the thread that loads the contents of the launcher:
  *   - workspace icons
@@ -84,7 +90,6 @@
  *   - deep shortcuts within apps
  */
 public class LoaderTask implements Runnable {
-    private static final boolean DEBUG_LOADERS = false;
     private static final String TAG = "LoaderTask";
 
     private final LauncherAppState mApp;
@@ -141,73 +146,66 @@
             }
         }
 
+        TraceHelper.beginSection(TAG);
         try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) {
-            long now = 0;
-            if (DEBUG_LOADERS) Log.d(TAG, "step 1.1: loading workspace");
+            TraceHelper.partitionSection(TAG, "step 1.1: loading UI resources");
+            loadUiResources();
+            TraceHelper.partitionSection(TAG, "step 1.2: loading workspace");
             loadWorkspace();
 
             verifyNotStopped();
-            if (DEBUG_LOADERS) Log.d(TAG, "step 1.2: bind workspace workspace");
+            TraceHelper.partitionSection(TAG, "step 1.2: bind workspace workspace");
             mResults.bindWorkspace();
 
             // Take a break
-            if (DEBUG_LOADERS) {
-                Log.d(TAG, "step 1 completed, wait for idle");
-                now = SystemClock.uptimeMillis();
-            }
+            TraceHelper.partitionSection(TAG, "step 1 completed, wait for idle");
             waitForIdle();
-            if (DEBUG_LOADERS) Log.d(TAG, "Waited " + (SystemClock.uptimeMillis() - now) + "ms");
             verifyNotStopped();
 
             // second step
-            if (DEBUG_LOADERS) Log.d(TAG, "step 2.1: loading all apps");
+            TraceHelper.partitionSection(TAG, "step 2.1: loading all apps");
             loadAllApps();
 
-            if (DEBUG_LOADERS) Log.d(TAG, "step 2.2: Binding all apps");
+            TraceHelper.partitionSection(TAG, "step 2.2: Binding all apps");
             verifyNotStopped();
             mResults.bindAllApps();
 
             verifyNotStopped();
-            if (DEBUG_LOADERS) Log.d(TAG, "step 2.3: Update icon cache");
+            TraceHelper.partitionSection(TAG, "step 2.3: Update icon cache");
             updateIconCache();
 
             // Take a break
-            if (DEBUG_LOADERS) {
-                Log.d(TAG, "step 2 completed, wait for idle");
-                now = SystemClock.uptimeMillis();
-            }
+            TraceHelper.partitionSection(TAG, "step 2 completed, wait for idle");
             waitForIdle();
-            if (DEBUG_LOADERS) Log.d(TAG, "Waited " + (SystemClock.uptimeMillis() - now) + "ms");
             verifyNotStopped();
 
             // third step
-            if (DEBUG_LOADERS) Log.d(TAG, "step 3.1: loading deep shortcuts");
+            TraceHelper.partitionSection(TAG, "step 3.1: loading deep shortcuts");
             loadDeepShortcuts();
 
             verifyNotStopped();
-            if (DEBUG_LOADERS) Log.d(TAG, "step 3.2: bind deep shortcuts");
+            TraceHelper.partitionSection(TAG, "step 3.2: bind deep shortcuts");
             mResults.bindDeepShortcuts();
 
             // Take a break
-            if (DEBUG_LOADERS) Log.d(TAG, "step 3 completed, wait for idle");
+            TraceHelper.partitionSection(TAG, "step 3 completed, wait for idle");
             waitForIdle();
             verifyNotStopped();
 
             // fourth step
-            if (DEBUG_LOADERS) Log.d(TAG, "step 4.1: loading widgets");
+            TraceHelper.partitionSection(TAG, "step 4.1: loading widgets");
             mBgDataModel.widgetsModel.update(mApp, null);
 
             verifyNotStopped();
-            if (DEBUG_LOADERS) Log.d(TAG, "step 4.2: Binding widgets");
+            TraceHelper.partitionSection(TAG, "step 4.2: Binding widgets");
             mResults.bindWidgets();
 
             transaction.commit();
         } catch (CancellationException e) {
             // Loader stopped, ignore
-            if (DEBUG_LOADERS) {
-                Log.d(TAG, "Loader cancelled", e);
-            }
+            TraceHelper.partitionSection(TAG, "Cancelled");
         }
+        TraceHelper.endSection(TAG);
     }
 
     public synchronized void stopLocked() {
@@ -215,11 +213,15 @@
         this.notify();
     }
 
-    private void loadWorkspace() {
-        if (LauncherAppState.PROFILE_STARTUP) {
-            Trace.beginSection("Loading Workspace");
+    public void loadUiResources() {
+        if (Utilities.ATLEAST_OREO) {
+            ClickShadowView.setAdaptiveIconScaleFactor(
+                    IconNormalizer.getInstance(mApp.getContext()).getScale(
+                            new AdaptiveIconDrawable(null, null), null, null, null));
         }
+    }
 
+    private void loadWorkspace() {
         final Context context = mApp.getContext();
         final ContentResolver contentResolver = context.getContentResolver();
         final PackageManagerHelper pmHelper = new PackageManagerHelper(context);
@@ -478,13 +480,13 @@
                                                     true /* badged */, fallbackIconProvider);
                                     if (pmHelper.isAppSuspended(
                                             pinnedShortcut.getPackage(), info.user)) {
-                                        info.isDisabled |= ShortcutInfo.FLAG_DISABLED_SUSPENDED;
+                                        info.runtimeStatusFlags |= FLAG_DISABLED_SUSPENDED;
                                     }
                                     intent = info.intent;
                                 } else {
                                     // Create a shortcut info in disabled mode for now.
                                     info = c.loadSimpleShortcut();
-                                    info.isDisabled |= ShortcutInfo.FLAG_DISABLED_LOCKED_USER;
+                                    info.runtimeStatusFlags |= FLAG_DISABLED_LOCKED_USER;
                                 }
                             } else { // item type == ITEM_TYPE_SHORTCUT
                                 info = c.loadSimpleShortcut();
@@ -492,7 +494,7 @@
                                 // Shortcuts are only available on the primary profile
                                 if (!TextUtils.isEmpty(targetPkg)
                                         && pmHelper.isAppSuspended(targetPkg, c.user)) {
-                                    disabledState |= ShortcutInfo.FLAG_DISABLED_SUSPENDED;
+                                    disabledState |= FLAG_DISABLED_SUSPENDED;
                                 }
 
                                 // App shortcuts that used to be automatically added to Launcher
@@ -515,9 +517,9 @@
                                 info.rank = c.getInt(rankIndex);
                                 info.spanX = 1;
                                 info.spanY = 1;
-                                info.isDisabled |= disabledState;
+                                info.runtimeStatusFlags |= disabledState;
                                 if (isSafeMode && !Utilities.isSystemApp(context, intent)) {
-                                    info.isDisabled |= ShortcutInfo.FLAG_DISABLED_SAFEMODE;
+                                    info.runtimeStatusFlags |= FLAG_DISABLED_SAFEMODE;
                                 }
 
                                 if (c.restoreFlag != 0 && !TextUtils.isEmpty(targetPkg)) {
@@ -734,7 +736,7 @@
                         numItemsInPreview++;
                     }
 
-                    if (numItemsInPreview >= FolderIcon.NUM_ITEMS_IN_PREVIEW) {
+                    if (numItemsInPreview >= MAX_NUM_ITEMS_IN_PREVIEW) {
                         break;
                     }
                 }
@@ -765,9 +767,6 @@
                 LauncherModel.updateWorkspaceScreenOrder(context, mBgDataModel.workspaceScreens);
             }
         }
-        if (LauncherAppState.PROFILE_STARTUP) {
-            Trace.endSection();
-        }
     }
 
     private void updateIconCache() {
@@ -792,21 +791,13 @@
     }
 
     private void loadAllApps() {
-        final long loadTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
-
         final List<UserHandle> profiles = mUserManager.getUserProfiles();
 
         // Clear the list of apps
         mBgAllAppsList.clear();
         for (UserHandle user : profiles) {
             // Query for the set of apps
-            final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
             final List<LauncherActivityInfo> apps = mLauncherApps.getActivityList(null, user);
-            if (DEBUG_LOADERS) {
-                Log.d(TAG, "getActivityList took "
-                        + (SystemClock.uptimeMillis()-qiaTime) + "ms for user " + user);
-                Log.d(TAG, "getActivityList got " + apps.size() + " apps for user " + user);
-            }
             // Fail if we don't have any apps
             // TODO: Fix this. Only fail for the current user.
             if (apps == null || apps.isEmpty()) {
@@ -833,10 +824,6 @@
         }
 
         mBgAllAppsList.added = new ArrayList<>();
-        if (DEBUG_LOADERS) {
-            Log.d(TAG, "All apps loaded in in "
-                    + (SystemClock.uptimeMillis() - loadTime) + "ms");
-        }
     }
 
     private void loadDeepShortcuts() {
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index 78ecbc6..470dadf 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -244,9 +244,9 @@
                                 infoUpdated = true;
                             }
 
-                            int oldDisabledFlags = si.isDisabled;
-                            si.isDisabled = flagOp.apply(si.isDisabled);
-                            if (si.isDisabled != oldDisabledFlags) {
+                            int oldRuntimeFlags = si.runtimeStatusFlags;
+                            si.runtimeStatusFlags = flagOp.apply(si.runtimeStatusFlags);
+                            if (si.runtimeStatusFlags != oldRuntimeFlags) {
                                 shortcutUpdated = true;
                             }
                         }
@@ -336,17 +336,7 @@
             });
         }
 
-        // Notify launcher of widget update. From marshmallow onwards we use AppWidgetHost to
-        // get widget update signals.
-        if (!Utilities.ATLEAST_MARSHMALLOW &&
-                (mOp == OP_ADD || mOp == OP_REMOVE || mOp == OP_UPDATE)) {
-            scheduleCallbackTask(new CallbackTask() {
-                @Override
-                public void execute(Callbacks callbacks) {
-                    callbacks.notifyWidgetProvidersChanged();
-                }
-            });
-        } else if (Utilities.ATLEAST_OREO && mOp == OP_ADD) {
+        if (Utilities.ATLEAST_OREO && mOp == OP_ADD) {
             // Load widgets for the new package. Changes due to app updates are handled through
             // AppWidgetHost events, this is just to initialize the long-press options.
             for (int i = 0; i < N; i++) {
diff --git a/src/com/android/launcher3/model/UserLockStateChangedTask.java b/src/com/android/launcher3/model/UserLockStateChangedTask.java
index 8170f9a..2e9ac72 100644
--- a/src/com/android/launcher3/model/UserLockStateChangedTask.java
+++ b/src/com/android/launcher3/model/UserLockStateChangedTask.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.model;
 
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER;
+
 import android.content.Context;
 import android.os.UserHandle;
 
@@ -87,12 +89,12 @@
                         removedKeys.add(key);
                         continue;
                     }
-                    si.isDisabled &= ~ShortcutInfo.FLAG_DISABLED_LOCKED_USER;
+                    si.runtimeStatusFlags &= ~FLAG_DISABLED_LOCKED_USER;
                     si.updateFromDeepShortcutInfo(shortcut, context);
                     si.iconBitmap = LauncherIcons.createShortcutIcon(shortcut, context,
                             si.iconBitmap);
                 } else {
-                    si.isDisabled |= ShortcutInfo.FLAG_DISABLED_LOCKED_USER;
+                    si.runtimeStatusFlags |= FLAG_DISABLED_LOCKED_USER;
                 }
                 updatedShortcutInfos.add(si);
             }
diff --git a/src/com/android/launcher3/model/WidgetsModel.java b/src/com/android/launcher3/model/WidgetsModel.java
index ed900bf..1ff0dac 100644
--- a/src/com/android/launcher3/model/WidgetsModel.java
+++ b/src/com/android/launcher3/model/WidgetsModel.java
@@ -15,6 +15,7 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.compat.AlphabeticIndexCompat;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.ShortcutConfigActivityInfo;
@@ -22,10 +23,14 @@
 import com.android.launcher3.util.MultiHashMap;
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.Preconditions;
+import com.android.launcher3.widget.WidgetItemComparator;
+import com.android.launcher3.widget.WidgetListRowEntry;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
+import java.util.Map;
 
 /**
  * Widgets data model that is used by the adapters of the widget views and controllers.
@@ -42,8 +47,26 @@
 
     private AppFilter mAppFilter;
 
-    public synchronized MultiHashMap<PackageItemInfo, WidgetItem> getWidgetsMap() {
-        return mWidgetsList.clone();
+    /**
+     * 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) {
+        ArrayList<WidgetListRowEntry> result = new ArrayList<>();
+        AlphabeticIndexCompat indexer = new AlphabeticIndexCompat(context);
+
+        WidgetItemComparator widgetComparator = new WidgetItemComparator();
+        for (Map.Entry<PackageItemInfo, ArrayList<WidgetItem>> entry : mWidgetsList.entrySet()) {
+            WidgetListRowEntry row = new WidgetListRowEntry(entry.getKey(), entry.getValue());
+            row.titleSectionName = indexer.computeSectionName(row.pkgItem.title);
+            Collections.sort(row.widgets, widgetComparator);
+            result.add(row);
+        }
+        return result;
     }
 
     /**
diff --git a/src/com/android/launcher3/notification/Interpolators.java b/src/com/android/launcher3/notification/Interpolators.java
deleted file mode 100644
index 5c3b22a..0000000
--- a/src/com/android/launcher3/notification/Interpolators.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * 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.notification;
-
-import android.view.animation.Interpolator;
-import android.view.animation.PathInterpolator;
-
-/**
- * Utility class to receive interpolators from.
- *
- * This class was copied from com.android.systemui.
- */
-public class Interpolators {
-    public static final Interpolator FAST_OUT_SLOW_IN = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
-    public static final Interpolator FAST_OUT_LINEAR_IN = new PathInterpolator(0.4f, 0f, 1f, 1f);
-    public static final Interpolator LINEAR_OUT_SLOW_IN = new PathInterpolator(0f, 0f, 0.2f, 1f);
-
-    /**
-     * Interpolator to be used when animating a move based on a click. Pair with enough duration.
-     */
-    public static final Interpolator TOUCH_RESPONSE =
-            new PathInterpolator(0.3f, 0f, 0.1f, 1f);
-}
diff --git a/src/com/android/launcher3/notification/NotificationFooterLayout.java b/src/com/android/launcher3/notification/NotificationFooterLayout.java
index ad07d37..1216a27 100644
--- a/src/com/android/launcher3/notification/NotificationFooterLayout.java
+++ b/src/com/android/launcher3/notification/NotificationFooterLayout.java
@@ -23,21 +23,18 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Rect;
-import android.graphics.drawable.ColorDrawable;
 import android.util.AttributeSet;
 import android.view.Gravity;
 import android.view.View;
-import android.view.ViewGroup;
 import android.widget.FrameLayout;
 import android.widget.LinearLayout;
 
-import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.PropertyListBuilder;
 import com.android.launcher3.anim.PropertyResetListener;
-import com.android.launcher3.popup.PopupContainerWithArrow;
+import com.android.launcher3.util.Themes;
 
 import java.util.ArrayList;
 import java.util.Iterator;
@@ -60,11 +57,12 @@
     private final List<NotificationInfo> mNotifications = new ArrayList<>();
     private final List<NotificationInfo> mOverflowNotifications = new ArrayList<>();
     private final boolean mRtl;
+    private final int mBackgroundColor;
 
     FrameLayout.LayoutParams mIconLayoutParams;
     private View mOverflowEllipsis;
     private LinearLayout mIconRow;
-    private int mBackgroundColor;
+    private NotificationItemView mContainer;
 
     public NotificationFooterLayout(Context context) {
         this(context, null, 0);
@@ -92,14 +90,19 @@
         int availableIconRowSpace = footerWidth - paddingEnd - ellipsisSpace
                 - iconSize * MAX_FOOTER_NOTIFICATIONS;
         mIconLayoutParams.setMarginStart(availableIconRowSpace / MAX_FOOTER_NOTIFICATIONS);
+
+        mBackgroundColor = Themes.getAttrColor(context, R.attr.popupColorPrimary);
     }
 
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
         mOverflowEllipsis = findViewById(R.id.overflow);
-        mIconRow = (LinearLayout) findViewById(R.id.icon_row);
-        mBackgroundColor = ((ColorDrawable) getBackground()).getColor();
+        mIconRow = findViewById(R.id.icon_row);
+    }
+
+    void setContainer(NotificationItemView container) {
+        mContainer = container;
     }
 
     /**
@@ -193,28 +196,12 @@
 
     private void removeViewFromIconRow(View child) {
         mIconRow.removeView(child);
-        mNotifications.remove((NotificationInfo) child.getTag());
+        mNotifications.remove(child.getTag());
         updateOverflowEllipsisVisibility();
         if (mIconRow.getChildCount() == 0) {
             // There are no more icons in the footer, so hide it.
-            PopupContainerWithArrow popup = PopupContainerWithArrow.getOpen(
-                    Launcher.getLauncher(getContext()));
-            if (popup != null) {
-                final int newHeight = getResources().getDimensionPixelSize(
-                        R.dimen.notification_empty_footer_height);
-                Animator collapseFooter = popup.reduceNotificationViewHeight(getHeight() - newHeight,
-                        getResources().getInteger(R.integer.config_removeNotificationViewDuration));
-                collapseFooter.addListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        ((ViewGroup) getParent()).findViewById(R.id.divider).setVisibility(GONE);
-                        // Keep view around because gutter is aligned to it, but remove height to
-                        // both hide the view and keep calculations correct for last dismissal.
-                        getLayoutParams().height = newHeight;
-                        requestLayout();
-                    }
-                });
-                collapseFooter.start();
+            if (mContainer != null) {
+                mContainer.removeFooter();
             }
         }
     }
diff --git a/src/com/android/launcher3/notification/NotificationGroup.java b/src/com/android/launcher3/notification/NotificationGroup.java
new file mode 100644
index 0000000..bce2117
--- /dev/null
+++ b/src/com/android/launcher3/notification/NotificationGroup.java
@@ -0,0 +1,52 @@
+/*
+ * 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.notification;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Contains data related to a group of notifications, like the group summary key and the child keys.
+ */
+public class NotificationGroup {
+    private String mGroupSummaryKey;
+    private Set<String> mChildKeys;
+
+    public NotificationGroup() {
+        mChildKeys = new HashSet<>();
+    }
+
+    public void setGroupSummaryKey(String groupSummaryKey) {
+        mGroupSummaryKey = groupSummaryKey;
+    }
+
+    public String getGroupSummaryKey() {
+        return mGroupSummaryKey;
+    }
+
+    public void addChildKey(String childKey) {
+        mChildKeys.add(childKey);
+    }
+
+    public void removeChildKey(String childKey) {
+        mChildKeys.remove(childKey);
+    }
+
+    public boolean isEmpty() {
+        return mChildKeys.isEmpty();
+    }
+}
diff --git a/src/com/android/launcher3/notification/NotificationInfo.java b/src/com/android/launcher3/notification/NotificationInfo.java
index 6e36f4f..120de04 100644
--- a/src/com/android/launcher3/notification/NotificationInfo.java
+++ b/src/com/android/launcher3/notification/NotificationInfo.java
@@ -31,7 +31,6 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.graphics.IconPalette;
-import com.android.launcher3.popup.PopupContainerWithArrow;
 import com.android.launcher3.util.PackageUserKey;
 
 /**
@@ -73,7 +72,7 @@
         if (icon == null) {
             // Use the small icon.
             icon = notification.getSmallIcon();
-            mIconDrawable = icon.loadDrawable(context);
+            mIconDrawable = icon == null ? null : icon.loadDrawable(context);
             mIconColor = statusBarNotification.getNotification().color;
             mIsIconLarge = false;
         } else {
@@ -110,7 +109,7 @@
             launcher.getPopupDataProvider().cancelNotification(notificationKey);
         }
         AbstractFloatingView.closeOpenContainer(launcher, AbstractFloatingView
-                .TYPE_POPUP_CONTAINER_WITH_ARROW);
+                .TYPE_ACTION_POPUP);
     }
 
     public Drawable getIconForBackground(Context context, int background) {
diff --git a/src/com/android/launcher3/notification/NotificationItemView.java b/src/com/android/launcher3/notification/NotificationItemView.java
index ab94c32..5bbd19c 100644
--- a/src/com/android/launcher3/notification/NotificationItemView.java
+++ b/src/com/android/launcher3/notification/NotificationItemView.java
@@ -16,117 +16,102 @@
 
 package com.android.launcher3.notification;
 
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
 import android.app.Notification;
 import android.content.Context;
 import android.graphics.Rect;
 import android.support.annotation.Nullable;
-import android.util.AttributeSet;
 import android.view.MotionEvent;
 import android.view.View;
-import android.widget.FrameLayout;
+import android.view.ViewGroup.MarginLayoutParams;
 import android.widget.TextView;
 
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.R;
-import com.android.launcher3.anim.PropertyResetListener;
-import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
 import com.android.launcher3.graphics.IconPalette;
-import com.android.launcher3.logging.UserEventDispatcher.LogContainerProvider;
-import com.android.launcher3.popup.PopupItemView;
+import com.android.launcher3.popup.PopupContainerWithArrow;
 import com.android.launcher3.touch.SwipeDetector;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.util.Themes;
 
 import java.util.List;
 
+import static com.android.launcher3.touch.SwipeDetector.HORIZONTAL;
+
 /**
- * A {@link FrameLayout} that contains a header, main view and a footer.
- * The main view contains the icon and text (title + subtext) of the first notification.
- * The footer contains: A list of just the icons of all the notifications past the first one.
- * @see NotificationFooterLayout
+ * Utility class to manage notification UI
  */
-public class NotificationItemView extends PopupItemView implements LogContainerProvider {
+public class NotificationItemView {
 
     private static final Rect sTempRect = new Rect();
 
-    private TextView mHeaderText;
-    private TextView mHeaderCount;
-    private NotificationMainView mMainView;
-    private NotificationFooterLayout mFooter;
-    private SwipeDetector mSwipeDetector;
+    private final Context mContext;
+    private final PopupContainerWithArrow mContainer;
+
+    private final TextView mHeaderText;
+    private final TextView mHeaderCount;
+    private final NotificationMainView mMainView;
+    private final NotificationFooterLayout mFooter;
+    private final SwipeDetector mSwipeDetector;
+    private final View mIconView;
+
+    private final View mHeader;
+    private final View mDivider;
+
+    private View mGutter;
+
+    private boolean mIgnoreTouch = false;
     private boolean mAnimatingNextIcon;
     private int mNotificationHeaderTextColor = Notification.COLOR_DEFAULT;
 
-    public NotificationItemView(Context context) {
-        this(context, null, 0);
-    }
+    public NotificationItemView(PopupContainerWithArrow container) {
+        mContainer = container;
+        mContext = container.getContext();
 
-    public NotificationItemView(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
+        mHeaderText = container.findViewById(R.id.notification_text);
+        mHeaderCount = container.findViewById(R.id.notification_count);
+        mMainView = container.findViewById(R.id.main_view);
+        mFooter = container.findViewById(R.id.footer);
+        mIconView = container.findViewById(R.id.popup_item_icon);
 
-    public NotificationItemView(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-    }
+        mHeader = container.findViewById(R.id.header);
+        mDivider = container.findViewById(R.id.divider);
 
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        mHeaderText = findViewById(R.id.notification_text);
-        mHeaderCount = findViewById(R.id.notification_count);
-        mMainView = findViewById(R.id.main_view);
-        mFooter = findViewById(R.id.footer);
-
-        mSwipeDetector = new SwipeDetector(getContext(), mMainView, SwipeDetector.HORIZONTAL);
+        mSwipeDetector = new SwipeDetector(mContext, mMainView, HORIZONTAL);
         mSwipeDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_BOTH, false);
         mMainView.setSwipeDetector(mSwipeDetector);
+        mFooter.setContainer(this);
     }
 
-    public NotificationMainView getMainView() {
-        return mMainView;
+    public void addGutter() {
+        if (mGutter == null) {
+            mGutter = mContainer.inflateAndAdd(R.layout.notification_gutter);
+        }
     }
 
-    /**
-     * This method is used to calculate the height to remove when dismissing the last notification.
-     * We subtract the height of the footer in this case since the footer should be gone or in the
-     * process of being removed.
-     * @return The height of the entire notification item, minus the footer if it still exists.
-     */
-    public int getHeightMinusFooter() {
-        if (mFooter.getParent() == null) {
-            return getHeight();
+    public void removeFooter() {
+        if (mContainer.indexOfChild(mFooter) >= 0) {
+            mContainer.removeView(mFooter);
+            mContainer.removeView(mDivider);
         }
-        int excessFooterHeight = mFooter.getHeight() - getResources().getDimensionPixelSize(
-                R.dimen.notification_empty_footer_height);
-        return getHeight() - excessFooterHeight;
     }
 
-    public Animator animateHeightRemoval(int heightToRemove, boolean shouldRemoveFromTop) {
-        AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
+    public void inverseGutterMargin() {
+        MarginLayoutParams lp = (MarginLayoutParams) mGutter.getLayoutParams();
+        int top = lp.topMargin;
+        lp.topMargin = lp.bottomMargin;
+        lp.bottomMargin = top;
+    }
 
-        Rect startRect = new Rect(mPillRect);
-        Rect endRect = new Rect(mPillRect);
-        if (shouldRemoveFromTop) {
-            endRect.top += heightToRemove;
-        } else {
-            endRect.bottom -= heightToRemove;
-        }
-        anim.play(new RoundedRectRevealOutlineProvider(getBackgroundRadius(), getBackgroundRadius(),
-                startRect, endRect, mRoundedCorners).createRevealAnimator(this, false));
+    public void removeAllViews() {
+        mContainer.removeView(mMainView);
+        mContainer.removeView(mHeader);
 
-        View bottomGutter = findViewById(R.id.gutter_bottom);
-        if (bottomGutter != null && bottomGutter.getVisibility() == VISIBLE) {
-            Animator translateGutter = ObjectAnimator.ofFloat(bottomGutter, TRANSLATION_Y,
-                    -heightToRemove);
-            translateGutter.addListener(new PropertyResetListener<>(TRANSLATION_Y, 0f));
-            anim.play(translateGutter);
+        if (mContainer.indexOfChild(mFooter) >= 0) {
+            mContainer.removeView(mFooter);
+            mContainer.removeView(mDivider);
         }
 
-        return anim;
+        if (mGutter != null) {
+            mContainer.removeView(mGutter);
+        }
     }
 
     public void updateHeader(int notificationCount, @Nullable IconPalette palette) {
@@ -134,32 +119,44 @@
         if (palette != null) {
             if (mNotificationHeaderTextColor == Notification.COLOR_DEFAULT) {
                 mNotificationHeaderTextColor =
-                        IconPalette.resolveContrastColor(getContext(), palette.dominantColor,
-                                Themes.getAttrColor(getContext(), R.attr.popupColorPrimary));
+                        IconPalette.resolveContrastColor(mContext, palette.dominantColor,
+                                Themes.getAttrColor(mContext, R.attr.popupColorPrimary));
             }
             mHeaderText.setTextColor(mNotificationHeaderTextColor);
             mHeaderCount.setTextColor(mNotificationHeaderTextColor);
         }
     }
 
-    @Override
     public boolean onInterceptTouchEvent(MotionEvent ev) {
+        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+            sTempRect.set(mMainView.getLeft(), mMainView.getTop(),
+                    mMainView.getRight(), mMainView.getBottom());
+            mIgnoreTouch = !sTempRect.contains((int) ev.getX(), (int) ev.getY());
+            if (!mIgnoreTouch) {
+                mContainer.getParent().requestDisallowInterceptTouchEvent(true);
+            }
+        }
+        if (mIgnoreTouch) {
+            return false;
+        }
         if (mMainView.getNotificationInfo() == null) {
             // The notification hasn't been populated yet.
             return false;
         }
-        getParent().requestDisallowInterceptTouchEvent(true);
+
         mSwipeDetector.onTouchEvent(ev);
         return mSwipeDetector.isDraggingOrSettling();
     }
 
-    @Override
     public boolean onTouchEvent(MotionEvent ev) {
+        if (mIgnoreTouch) {
+            return false;
+        }
         if (mMainView.getNotificationInfo() == null) {
             // The notification hasn't been populated yet.
             return false;
         }
-        return mSwipeDetector.onTouchEvent(ev) || super.onTouchEvent(ev);
+        return mSwipeDetector.onTouchEvent(ev);
     }
 
     public void applyNotificationInfos(final List<NotificationInfo> notificationInfos) {
@@ -168,7 +165,7 @@
         }
 
         NotificationInfo mainNotification = notificationInfos.get(0);
-        mMainView.applyNotificationInfo(mainNotification, mIconView);
+        mMainView.applyNotificationInfo(mainNotification, false);
 
         for (int i = 1; i < notificationInfos.size(); i++) {
             mFooter.addNotificationInfo(notificationInfos.get(i));
@@ -182,29 +179,18 @@
         if (dismissedMainNotification && !mAnimatingNextIcon) {
             // Animate the next icon into place as the new main notification.
             mAnimatingNextIcon = true;
-            mMainView.setVisibility(INVISIBLE);
-            mMainView.setTranslationX(0);
+            mMainView.setContentVisibility(View.INVISIBLE);
+            mMainView.setContentTranslation(0);
             mIconView.getGlobalVisibleRect(sTempRect);
-            mFooter.animateFirstNotificationTo(sTempRect,
-                    new NotificationFooterLayout.IconAnimationEndListener() {
-                @Override
-                public void onIconAnimationEnd(NotificationInfo newMainNotification) {
-                    if (newMainNotification != null) {
-                        mMainView.applyNotificationInfo(newMainNotification, mIconView, true);
-                        mMainView.setVisibility(VISIBLE);
-                    }
-                    mAnimatingNextIcon = false;
+            mFooter.animateFirstNotificationTo(sTempRect, (newMainNotification) -> {
+                if (newMainNotification != null) {
+                    mMainView.applyNotificationInfo(newMainNotification, true);
+                    mMainView.setContentVisibility(View.VISIBLE);
                 }
+                mAnimatingNextIcon = false;
             });
         } else {
             mFooter.trimNotifications(notificationKeys);
         }
     }
-
-    @Override
-    public void fillInLogContainerData(View v, ItemInfo info, LauncherLogProto.Target target,
-            LauncherLogProto.Target targetParent) {
-        target.itemType = LauncherLogProto.ItemType.NOTIFICATION;
-        targetParent.containerType = LauncherLogProto.ContainerType.DEEPSHORTCUTS;
-    }
 }
diff --git a/src/com/android/launcher3/notification/NotificationListener.java b/src/com/android/launcher3/notification/NotificationListener.java
index 9126626..7b70df7 100644
--- a/src/com/android/launcher3/notification/NotificationListener.java
+++ b/src/com/android/launcher3/notification/NotificationListener.java
@@ -38,7 +38,9 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 import static com.android.launcher3.SettingsActivity.NOTIFICATION_BADGING;
@@ -66,6 +68,8 @@
     private final Handler mWorkerHandler;
     private final Handler mUiHandler;
     private final Ranking mTempRanking = new Ranking();
+    /** Maps groupKey's to the corresponding group of notifications. */
+    private final Map<String, NotificationGroup> mNotificationGroupMap = new HashMap<>();
 
     private SettingsObserver mNotificationBadgingObserver;
 
@@ -227,6 +231,15 @@
                         NotificationKeyData.fromNotification(sbn));
         mWorkerHandler.obtainMessage(MSG_NOTIFICATION_REMOVED, packageUserKeyAndNotificationKey)
                 .sendToTarget();
+
+        NotificationGroup notificationGroup = mNotificationGroupMap.get(sbn.getGroupKey());
+        if (notificationGroup != null) {
+            notificationGroup.removeChildKey(sbn.getKey());
+            if (notificationGroup.isEmpty()) {
+                cancelNotification(notificationGroup.getGroupSummaryKey());
+                mNotificationGroupMap.remove(sbn.getGroupKey());
+            }
+        }
     }
 
     /** This makes a potentially expensive binder call and should be run on a background thread. */
@@ -264,18 +277,34 @@
     }
 
     private boolean shouldBeFilteredOut(StatusBarNotification sbn) {
+        Notification notification = sbn.getNotification();
+
+        boolean isGroupHeader = (notification.flags & Notification.FLAG_GROUP_SUMMARY) != 0;
+        if (sbn.isGroup()) {
+            // Maintain group info so we can cancel the summary when the last child is canceled.
+            NotificationGroup notificationGroup = mNotificationGroupMap.get(sbn.getGroupKey());
+            if (notificationGroup == null) {
+                notificationGroup = new NotificationGroup();
+                mNotificationGroupMap.put(sbn.getGroupKey(), notificationGroup);
+            }
+            if (isGroupHeader) {
+                notificationGroup.setGroupSummaryKey(sbn.getKey());
+            } else {
+                notificationGroup.addChildKey(sbn.getKey());
+            }
+        }
+
         getCurrentRanking().getRanking(sbn.getKey(), mTempRanking);
         if (!mTempRanking.canShowBadge()) {
             return true;
         }
-        Notification notification = sbn.getNotification();
         if (mTempRanking.getChannel().getId().equals(NotificationChannel.DEFAULT_CHANNEL_ID)) {
             // Special filtering for the default, legacy "Miscellaneous" channel.
             if ((notification.flags & Notification.FLAG_ONGOING_EVENT) != 0) {
                 return true;
             }
         }
-        boolean isGroupHeader = (notification.flags & Notification.FLAG_GROUP_SUMMARY) != 0;
+
         CharSequence title = notification.extras.getCharSequence(Notification.EXTRA_TITLE);
         CharSequence text = notification.extras.getCharSequence(Notification.EXTRA_TEXT);
         boolean missingTitleAndText = TextUtils.isEmpty(title) && TextUtils.isEmpty(text);
diff --git a/src/com/android/launcher3/notification/NotificationMainView.java b/src/com/android/launcher3/notification/NotificationMainView.java
index 5aff28d..33caded 100644
--- a/src/com/android/launcher3/notification/NotificationMainView.java
+++ b/src/com/android/launcher3/notification/NotificationMainView.java
@@ -16,22 +16,28 @@
 
 package com.android.launcher3.notification;
 
+import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
+
+import android.animation.Animator;
 import android.animation.ObjectAnimator;
+import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.RippleDrawable;
+import android.os.Build;
 import android.text.TextUtils;
 import android.util.AttributeSet;
+import android.util.FloatProperty;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewPropertyAnimator;
 import android.widget.FrameLayout;
 import android.widget.TextView;
 
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
+import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.touch.OverScroll;
 import com.android.launcher3.touch.SwipeDetector;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
@@ -41,13 +47,33 @@
  * A {@link android.widget.FrameLayout} that contains a single notification,
  * e.g. icon + title + text.
  */
+@TargetApi(Build.VERSION_CODES.N)
 public class NotificationMainView extends FrameLayout implements SwipeDetector.Listener {
 
+    private static FloatProperty<NotificationMainView> CONTENT_TRANSLATION =
+            new FloatProperty<NotificationMainView>("contentTranslation") {
+        @Override
+        public void setValue(NotificationMainView view, float v) {
+            view.setContentTranslation(v);
+        }
+
+        @Override
+        public Float get(NotificationMainView view) {
+            return view.mTextAndBackground.getTranslationX();
+        }
+    };
+
+    // This is used only to track the notification view, so that it can be properly logged.
+    public static final ItemInfo NOTIFICATION_ITEM_INFO = new ItemInfo();
+
+    private final ObjectAnimator mContentTranslateAnimator;
+
     private NotificationInfo mNotificationInfo;
     private ViewGroup mTextAndBackground;
     private int mBackgroundColor;
     private TextView mTitleView;
     private TextView mTextView;
+    private View mIconView;
 
     private SwipeDetector mSwipeDetector;
 
@@ -61,25 +87,24 @@
 
     public NotificationMainView(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
+
+        mContentTranslateAnimator = ObjectAnimator.ofFloat(this, CONTENT_TRANSLATION, 0);
     }
 
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
 
-        mTextAndBackground = (ViewGroup) findViewById(R.id.text_and_background);
+        mTextAndBackground = findViewById(R.id.text_and_background);
         ColorDrawable colorBackground = (ColorDrawable) mTextAndBackground.getBackground();
         mBackgroundColor = colorBackground.getColor();
         RippleDrawable rippleBackground = new RippleDrawable(ColorStateList.valueOf(
                 Themes.getAttrColor(getContext(), android.R.attr.colorControlHighlight)),
                 colorBackground, null);
         mTextAndBackground.setBackground(rippleBackground);
-        mTitleView = (TextView) mTextAndBackground.findViewById(R.id.title);
-        mTextView = (TextView) mTextAndBackground.findViewById(R.id.text);
-    }
-
-    public void applyNotificationInfo(NotificationInfo mainNotification, View iconView) {
-        applyNotificationInfo(mainNotification, iconView, false);
+        mTitleView = mTextAndBackground.findViewById(R.id.title);
+        mTextView = mTextAndBackground.findViewById(R.id.text);
+        mIconView = findViewById(R.id.popup_item_icon);
     }
 
     public void setSwipeDetector(SwipeDetector swipeDetector) {
@@ -89,8 +114,7 @@
     /**
      * Sets the content of this view, animating it after a new icon shifts up if necessary.
      */
-    public void applyNotificationInfo(NotificationInfo mainNotification, View iconView,
-           boolean animate) {
+    public void applyNotificationInfo(NotificationInfo mainNotification, boolean animate) {
         mNotificationInfo = mainNotification;
         CharSequence title = mNotificationInfo.title;
         CharSequence text = mNotificationInfo.text;
@@ -102,20 +126,30 @@
             mTitleView.setText(TextUtils.isEmpty(title) ? text.toString() : title.toString());
             mTextView.setVisibility(GONE);
         }
-        iconView.setBackground(mNotificationInfo.getIconForBackground(getContext(),
+        mIconView.setBackground(mNotificationInfo.getIconForBackground(getContext(),
                 mBackgroundColor));
         if (mNotificationInfo.intent != null) {
             setOnClickListener(mNotificationInfo);
         }
-        setTranslationX(0);
+        setContentTranslation(0);
         // Add a dummy ItemInfo so that logging populates the correct container and item types
         // instead of DEFAULT_CONTAINERTYPE and DEFAULT_ITEMTYPE, respectively.
-        setTag(new ItemInfo());
+        setTag(NOTIFICATION_ITEM_INFO);
         if (animate) {
             ObjectAnimator.ofFloat(mTextAndBackground, ALPHA, 0, 1).setDuration(150).start();
         }
     }
 
+    public void setContentTranslation(float translation) {
+        mTextAndBackground.setTranslationX(translation);
+        mIconView.setTranslationX(translation);
+    }
+
+    public void setContentVisibility(int visibility) {
+        mTextAndBackground.setVisibility(visibility);
+        mIconView.setVisibility(visibility);
+    }
+
     public NotificationInfo getNotificationInfo() {
         return mNotificationInfo;
     }
@@ -142,9 +176,9 @@
 
     @Override
     public boolean onDrag(float displacement, float velocity) {
-        setTranslationX(canChildBeDismissed()
+        setContentTranslation(canChildBeDismissed()
                 ? displacement : OverScroll.dampedScroll(displacement, getWidth()));
-        animate().cancel();
+        mContentTranslateAnimator.cancel();
         return true;
     }
 
@@ -152,6 +186,7 @@
     public void onDragEnd(float velocity, boolean fling) {
         final boolean willExit;
         final float endTranslation;
+        final float startTranslation = mTextAndBackground.getTranslationX();
 
         if (!canChildBeDismissed()) {
             willExit = false;
@@ -159,31 +194,30 @@
         } else if (fling) {
             willExit = true;
             endTranslation = velocity < 0 ? - getWidth() : getWidth();
-        } else if (Math.abs(getTranslationX()) > getWidth() / 2) {
+        } else if (Math.abs(startTranslation) > getWidth() / 2) {
             willExit = true;
-            endTranslation = (getTranslationX() < 0 ? -getWidth() : getWidth());
+            endTranslation = (startTranslation < 0 ? -getWidth() : getWidth());
         } else {
             willExit = false;
             endTranslation = 0;
         }
 
-        SwipeDetector.ScrollInterpolator interpolator = new SwipeDetector.ScrollInterpolator();
-        interpolator.setVelocityAtZero(velocity);
-
         long duration = SwipeDetector.calculateDuration(velocity,
-                (endTranslation - getTranslationX()) / getWidth());
-        animate()
-                .setDuration(duration)
-                .setInterpolator(interpolator)
-                .translationX(endTranslation)
-                .withEndAction(new Runnable() {
-                    @Override
-                    public void run() {
-                        mSwipeDetector.finishedScrolling();
-                        if (willExit) {
-                            onChildDismissed();
-                        }
-                    }
-                }).start();
+                (endTranslation - startTranslation) / getWidth());
+
+        mContentTranslateAnimator.removeAllListeners();
+        mContentTranslateAnimator.setDuration(duration)
+                .setInterpolator(scrollInterpolatorForVelocity(velocity));
+        mContentTranslateAnimator.setFloatValues(startTranslation, endTranslation);
+        mContentTranslateAnimator.addListener(new AnimationSuccessListener() {
+            @Override
+            public void onAnimationSuccess(Animator animator) {
+                mSwipeDetector.finishedScrolling();
+                if (willExit) {
+                    onChildDismissed();
+                }
+            }
+        });
+        mContentTranslateAnimator.start();
     }
 }
diff --git a/src/com/android/launcher3/pageindicators/CaretDrawable.java b/src/com/android/launcher3/pageindicators/CaretDrawable.java
deleted file mode 100644
index 5ade497..0000000
--- a/src/com/android/launcher3/pageindicators/CaretDrawable.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright (C) 2016 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.pageindicators;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.ColorFilter;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.PixelFormat;
-import android.graphics.drawable.Drawable;
-
-import com.android.launcher3.R;
-import com.android.launcher3.util.Themes;
-
-public class CaretDrawable extends Drawable {
-    public static final float PROGRESS_CARET_POINTING_UP = -1f;
-    public static final float PROGRESS_CARET_POINTING_DOWN = 1f;
-    public static final float PROGRESS_CARET_NEUTRAL = 0;
-
-    private float mCaretProgress = PROGRESS_CARET_NEUTRAL;
-
-    private Paint mShadowPaint = new Paint();
-    private Paint mCaretPaint = new Paint();
-    private Path mPath = new Path();
-    private final int mCaretSizePx;
-    private final boolean mUseShadow;
-
-    public CaretDrawable(Context context) {
-        final Resources res = context.getResources();
-
-        final int strokeWidth = res.getDimensionPixelSize(R.dimen.all_apps_caret_stroke_width);
-        final int shadowSpread = res.getDimensionPixelSize(R.dimen.all_apps_caret_shadow_spread);
-
-        mCaretPaint.setColor(Themes.getAttrColor(context, R.attr.workspaceTextColor));
-        mCaretPaint.setAntiAlias(true);
-        mCaretPaint.setStrokeWidth(strokeWidth);
-        mCaretPaint.setStyle(Paint.Style.STROKE);
-        mCaretPaint.setStrokeCap(Paint.Cap.ROUND);
-        mCaretPaint.setStrokeJoin(Paint.Join.ROUND);
-
-        mShadowPaint.setColor(res.getColor(R.color.default_shadow_color_no_alpha));
-        mShadowPaint.setAlpha(Themes.getAlpha(context, android.R.attr.spotShadowAlpha));
-        mShadowPaint.setAntiAlias(true);
-        mShadowPaint.setStrokeWidth(strokeWidth + (shadowSpread * 2));
-        mShadowPaint.setStyle(Paint.Style.STROKE);
-        mShadowPaint.setStrokeCap(Paint.Cap.ROUND);
-        mShadowPaint.setStrokeJoin(Paint.Join.ROUND);
-
-        mUseShadow = !Themes.getAttrBoolean(context, R.attr.isWorkspaceDarkText);
-        mCaretSizePx = res.getDimensionPixelSize(R.dimen.all_apps_caret_size);
-    }
-
-    @Override
-    public int getIntrinsicHeight() {
-        return mCaretSizePx;
-    }
-
-    @Override
-    public int getIntrinsicWidth() {
-        return mCaretSizePx;
-    }
-
-    @Override
-    public void draw(Canvas canvas) {
-        // Assumes caret paint is more important than shadow paint
-        if (Float.compare(mCaretPaint.getAlpha(), 0f) == 0) {
-            return;
-        }
-
-        // Assumes shadow stroke width is larger
-        final float width = getBounds().width() - mShadowPaint.getStrokeWidth();
-        final float height = getBounds().height() - mShadowPaint.getStrokeWidth();
-        final float left = getBounds().left + (mShadowPaint.getStrokeWidth() / 2);
-        final float top = getBounds().top + (mShadowPaint.getStrokeWidth() / 2);
-
-        // When the bounds are square, this will result in a caret with a right angle
-        final float verticalInset = (height / 4);
-        final float caretHeight = (height - (verticalInset * 2));
-
-        mPath.reset();
-        mPath.moveTo(left, top + caretHeight * (1 - getNormalizedCaretProgress()));
-        mPath.lineTo(left + (width / 2), top + caretHeight * getNormalizedCaretProgress());
-        mPath.lineTo(left + width, top + caretHeight * (1 - getNormalizedCaretProgress()));
-        if (mUseShadow) {
-            canvas.drawPath(mPath, mShadowPaint);
-        }
-        canvas.drawPath(mPath, mCaretPaint);
-    }
-
-    /**
-     * Sets the caret progress
-     *
-     * @param progress The progress ({@value #PROGRESS_CARET_POINTING_UP} for pointing up,
-     * {@value #PROGRESS_CARET_POINTING_DOWN} for pointing down, {@value #PROGRESS_CARET_NEUTRAL}
-     * for neutral)
-     */
-    public void setCaretProgress(float progress) {
-        mCaretProgress = progress;
-        invalidateSelf();
-    }
-
-    /**
-     * Returns the caret progress
-     *
-     * @return The progress
-     */
-    public float getCaretProgress() {
-        return mCaretProgress;
-    }
-
-    /**
-     * Returns the caret progress normalized to [0..1]
-     *
-     * @return The normalized progress
-     */
-    public float getNormalizedCaretProgress() {
-        return (mCaretProgress - PROGRESS_CARET_POINTING_UP) /
-                (PROGRESS_CARET_POINTING_DOWN - PROGRESS_CARET_POINTING_UP);
-    }
-
-    @Override
-    public int getOpacity() {
-        return PixelFormat.TRANSLUCENT;
-    }
-
-    @Override
-    public void setAlpha(int alpha) {
-        mCaretPaint.setAlpha(alpha);
-        mShadowPaint.setAlpha(alpha);
-        invalidateSelf();
-    }
-
-    @Override
-    public void setColorFilter(ColorFilter cf) {
-        // no-op
-    }
-}
diff --git a/src/com/android/launcher3/pageindicators/PageIndicator.java b/src/com/android/launcher3/pageindicators/PageIndicator.java
index 47c2ffb..be6bcc5 100644
--- a/src/com/android/launcher3/pageindicators/PageIndicator.java
+++ b/src/com/android/launcher3/pageindicators/PageIndicator.java
@@ -16,17 +16,13 @@
 package com.android.launcher3.pageindicators;
 
 import android.content.Context;
-import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.widget.FrameLayout;
 
-import com.android.launcher3.dynamicui.ExtractedColors;
-
 /**
  * Base class for a page indicator.
  */
 public abstract class PageIndicator extends FrameLayout {
-    private CaretDrawable mCaretDrawable;
 
     protected int mNumPages = 1;
 
@@ -54,30 +50,7 @@
         onPageCountChanged();
     }
 
-    public CaretDrawable getCaretDrawable() {
-        return mCaretDrawable;
-    }
-
-    public void setCaretDrawable(CaretDrawable caretDrawable) {
-        if (mCaretDrawable != null) {
-            mCaretDrawable.setCallback(null);
-        }
-
-        mCaretDrawable = caretDrawable;
-
-        if (mCaretDrawable != null) {
-            mCaretDrawable.setCallback(this);
-        }
-    }
-
     protected void onPageCountChanged() {}
 
     public void setShouldAutoHide(boolean shouldAutoHide) {}
-
-    public void updateColor(ExtractedColors extractedColors) {}
-
-    @Override
-    protected boolean verifyDrawable(Drawable who) {
-        return super.verifyDrawable(who) || who == getCaretDrawable();
-    }
 }
diff --git a/src/com/android/launcher3/pageindicators/PageIndicatorCaretLandscape.java b/src/com/android/launcher3/pageindicators/PageIndicatorCaretLandscape.java
deleted file mode 100644
index 911be93..0000000
--- a/src/com/android/launcher3/pageindicators/PageIndicatorCaretLandscape.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2016 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.pageindicators;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-
-import com.android.launcher3.Launcher;
-import com.android.launcher3.R;
-
-/**
- * Simply draws the caret drawable bottom-right aligned in the view. This ensures that we can have
- * a view with as large an area as we want (for touching) while maintaining a caret of size
- * all_apps_caret_size.  Used only for the landscape layout.
- */
-public class PageIndicatorCaretLandscape extends PageIndicator {
-    // all apps pull up handle drawable.
-
-    public PageIndicatorCaretLandscape(Context context) {
-        this(context, null);
-    }
-
-    public PageIndicatorCaretLandscape(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public PageIndicatorCaretLandscape(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-
-        int caretSize = context.getResources().getDimensionPixelSize(R.dimen.all_apps_caret_size);
-        CaretDrawable caretDrawable = new CaretDrawable(context);
-        caretDrawable.setBounds(0, 0, caretSize, caretSize);
-        setCaretDrawable(caretDrawable);
-
-        Launcher l = Launcher.getLauncher(context);
-        setOnClickListener(l);
-        setOnFocusChangeListener(l.mFocusHandler);
-    }
-
-    @Override
-    protected void onDraw(Canvas canvas) {
-        Rect drawableBounds = getCaretDrawable().getBounds();
-        int count = canvas.save();
-        canvas.translate((getWidth() - drawableBounds.width()) / 2,
-                getHeight() - drawableBounds.height());
-        getCaretDrawable().draw(canvas);
-        canvas.restoreToCount(count);
-    }
-}
diff --git a/src/com/android/launcher3/pageindicators/PageIndicatorLandscape.java b/src/com/android/launcher3/pageindicators/PageIndicatorLandscape.java
new file mode 100644
index 0000000..7325235
--- /dev/null
+++ b/src/com/android/launcher3/pageindicators/PageIndicatorLandscape.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2016 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.pageindicators;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import com.android.launcher3.Launcher;
+
+/**
+ * Simply draws the caret drawable bottom-right aligned in the view. This ensures that we can have
+ * a view with as large an area as we want (for touching) while maintaining a caret of size
+ * all_apps_caret_size.  Used only for the landscape layout.
+ */
+public class PageIndicatorLandscape extends PageIndicator {
+    // all apps pull up handle drawable.
+
+    public PageIndicatorLandscape(Context context) {
+        this(context, null);
+    }
+
+    public PageIndicatorLandscape(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public PageIndicatorLandscape(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        Launcher l = Launcher.getLauncher(context);
+        setOnClickListener(l);
+        setOnFocusChangeListener(l.mFocusHandler);
+    }
+}
diff --git a/src/com/android/launcher3/pageindicators/PageIndicatorLineCaret.java b/src/com/android/launcher3/pageindicators/PageIndicatorLine.java
similarity index 73%
rename from src/com/android/launcher3/pageindicators/PageIndicatorLineCaret.java
rename to src/com/android/launcher3/pageindicators/PageIndicatorLine.java
index 6281fec..09a06b0 100644
--- a/src/com/android/launcher3/pageindicators/PageIndicatorLineCaret.java
+++ b/src/com/android/launcher3/pageindicators/PageIndicatorLine.java
@@ -11,9 +11,7 @@
 import android.graphics.Paint;
 import android.os.Handler;
 import android.os.Looper;
-import android.support.v4.graphics.ColorUtils;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.util.Property;
 import android.view.ViewConfiguration;
 import android.widget.ImageView;
@@ -21,8 +19,6 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.dynamicui.ExtractedColors;
 import com.android.launcher3.dynamicui.WallpaperColorInfo;
 
 /**
@@ -30,10 +26,7 @@
  *
  * The fraction is 1 / number of pages and the position is based on the progress of the page scroll.
  */
-public class PageIndicatorLineCaret extends PageIndicator {
-    private static final String TAG = "PageIndicatorLine";
-
-    private static final int[] sTempCoords = new int[2];
+public class PageIndicatorLine extends PageIndicator {
 
     private static final int LINE_ANIMATE_DURATION = ViewConfiguration.getScrollBarFadeDuration();
     private static final int LINE_FADE_DELAY = ViewConfiguration.getScrollDefaultDelay();
@@ -63,43 +56,43 @@
     private final int mLineHeight;
     private ImageView mAllAppsHandle;
 
-    private static final Property<PageIndicatorLineCaret, Integer> PAINT_ALPHA
-            = new Property<PageIndicatorLineCaret, Integer>(Integer.class, "paint_alpha") {
+    private static final Property<PageIndicatorLine, Integer> PAINT_ALPHA
+            = new Property<PageIndicatorLine, Integer>(Integer.class, "paint_alpha") {
         @Override
-        public Integer get(PageIndicatorLineCaret obj) {
+        public Integer get(PageIndicatorLine obj) {
             return obj.mLinePaint.getAlpha();
         }
 
         @Override
-        public void set(PageIndicatorLineCaret obj, Integer alpha) {
+        public void set(PageIndicatorLine obj, Integer alpha) {
             obj.mLinePaint.setAlpha(alpha);
             obj.invalidate();
         }
     };
 
-    private static final Property<PageIndicatorLineCaret, Float> NUM_PAGES
-            = new Property<PageIndicatorLineCaret, Float>(Float.class, "num_pages") {
+    private static final Property<PageIndicatorLine, Float> NUM_PAGES
+            = new Property<PageIndicatorLine, Float>(Float.class, "num_pages") {
         @Override
-        public Float get(PageIndicatorLineCaret obj) {
+        public Float get(PageIndicatorLine obj) {
             return obj.mNumPagesFloat;
         }
 
         @Override
-        public void set(PageIndicatorLineCaret obj, Float numPages) {
+        public void set(PageIndicatorLine obj, Float numPages) {
             obj.mNumPagesFloat = numPages;
             obj.invalidate();
         }
     };
 
-    private static final Property<PageIndicatorLineCaret, Integer> TOTAL_SCROLL
-            = new Property<PageIndicatorLineCaret, Integer>(Integer.class, "total_scroll") {
+    private static final Property<PageIndicatorLine, Integer> TOTAL_SCROLL
+            = new Property<PageIndicatorLine, Integer>(Integer.class, "total_scroll") {
         @Override
-        public Integer get(PageIndicatorLineCaret obj) {
+        public Integer get(PageIndicatorLine obj) {
             return obj.mTotalScroll;
         }
 
         @Override
-        public void set(PageIndicatorLineCaret obj, Integer totalScroll) {
+        public void set(PageIndicatorLine obj, Integer totalScroll) {
             obj.mTotalScroll = totalScroll;
             obj.invalidate();
         }
@@ -112,15 +105,15 @@
         }
     };
 
-    public PageIndicatorLineCaret(Context context) {
+    public PageIndicatorLine(Context context) {
         this(context, null);
     }
 
-    public PageIndicatorLineCaret(Context context, AttributeSet attrs) {
+    public PageIndicatorLine(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
     }
 
-    public PageIndicatorLineCaret(Context context, AttributeSet attrs, int defStyle) {
+    public PageIndicatorLine(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
 
         Resources res = context.getResources();
@@ -129,7 +122,6 @@
 
         mLauncher = Launcher.getLauncher(context);
         mLineHeight = res.getDimensionPixelSize(R.dimen.dynamic_grid_page_indicator_line_height);
-        setCaretDrawable(new CaretDrawable(context));
 
         boolean darkText = WallpaperColorInfo.getInstance(context).supportsDarkText();
         mActiveAlpha = darkText ? BLACK_ALPHA : WHITE_ALPHA;
@@ -139,8 +131,7 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        mAllAppsHandle = (ImageView) findViewById(R.id.all_apps_handle);
-        mAllAppsHandle.setImageDrawable(getCaretDrawable());
+        mAllAppsHandle = findViewById(R.id.all_apps_handle);
         mAllAppsHandle.setOnClickListener(mLauncher);
         mAllAppsHandle.setOnFocusChangeListener(mLauncher.mFocusHandler);
         mLauncher.setAllAppsButton(mAllAppsHandle);
@@ -218,32 +209,6 @@
         }
     }
 
-    /**
-     * The line's color will be:
-     * - mostly opaque white if the hotseat is white (ignoring alpha)
-     * - mostly opaque black if the hotseat is black (ignoring alpha)
-     */
-    public void updateColor(ExtractedColors extractedColors) {
-        if (FeatureFlags.LAUNCHER3_GRADIENT_ALL_APPS) {
-            return;
-        }
-        int originalLineAlpha = mLinePaint.getAlpha();
-        int color = extractedColors.getColor(ExtractedColors.HOTSEAT_INDEX);
-        if (color != Color.TRANSPARENT) {
-            color = ColorUtils.setAlphaComponent(color, 255);
-            if (color == Color.BLACK) {
-                mActiveAlpha = BLACK_ALPHA;
-            } else if (color == Color.WHITE) {
-                mActiveAlpha = WHITE_ALPHA;
-            } else {
-                Log.e(TAG, "Setting workspace page indicators to an unsupported color: #"
-                        + Integer.toHexString(color));
-            }
-            mLinePaint.setColor(color);
-            mLinePaint.setAlpha(originalLineAlpha);
-        }
-    }
-
     private void animateLineToAlpha(int alpha) {
         if (alpha == mToAlpha) {
             // Ignore the new animation if it is going to the same alpha as the current animation.
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 8441598..3dc58a1 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -19,6 +19,7 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
+import android.animation.LayoutTransition;
 import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
@@ -35,20 +36,23 @@
 import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
-import android.support.annotation.IntDef;
 import android.util.AttributeSet;
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.ViewOutlineProvider;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.AccelerateDecelerateInterpolator;
+import android.widget.ImageView;
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.DragSource;
 import com.android.launcher3.DropTarget;
+import com.android.launcher3.DropTarget.DragObject;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAnimUtils;
@@ -57,82 +61,100 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
 import com.android.launcher3.accessibility.ShortcutMenuAccessibilityDelegate;
-import com.android.launcher3.anim.PropertyListBuilder;
-import com.android.launcher3.anim.PropertyResetListener;
 import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
 import com.android.launcher3.badge.BadgeInfo;
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.dragndrop.DragView;
 import com.android.launcher3.graphics.IconPalette;
 import com.android.launcher3.graphics.TriangleShape;
+import com.android.launcher3.logging.LoggerUtils;
+import com.android.launcher3.notification.NotificationInfo;
 import com.android.launcher3.notification.NotificationItemView;
 import com.android.launcher3.notification.NotificationKeyData;
 import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.shortcuts.DeepShortcutView;
-import com.android.launcher3.shortcuts.ShortcutsItemView;
+import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.Themes;
 
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
+import static com.android.launcher3.notification.NotificationMainView.NOTIFICATION_ITEM_INFO;
+import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS;
 import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS_IF_NOTIFICATIONS;
 import static com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import static com.android.launcher3.userevent.nano.LauncherLogProto.ItemType;
 import static com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 
 /**
- * A container for shortcuts to deep links within apps.
+ * A container for shortcuts to deep links and notifications associated with an app.
  */
 @TargetApi(Build.VERSION_CODES.N)
 public class PopupContainerWithArrow extends AbstractFloatingView implements DragSource,
-        DragController.DragListener {
+        DragController.DragListener, View.OnLongClickListener,
+        View.OnTouchListener {
 
-    public static final int ROUNDED_TOP_CORNERS = 1 << 0;
-    public static final int ROUNDED_BOTTOM_CORNERS = 1 << 1;
+    private final List<DeepShortcutView> mShortcuts = new ArrayList<>();
+    private final PointF mInterceptTouchDown = new PointF();
+    private final Rect mTempRect = new Rect();
+    private final Point mIconLastTouchPos = new Point();
 
-    @IntDef(flag = true, value = {
-            ROUNDED_TOP_CORNERS,
-            ROUNDED_BOTTOM_CORNERS
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public  @interface RoundedCornerFlags {}
-
-    protected final Launcher mLauncher;
     private final int mStartDragThreshold;
-    private LauncherAccessibilityDelegate mAccessibilityDelegate;
+    private final LayoutInflater mInflater;
+    private final float mOutlineRadius;
+    private final Launcher mLauncher;
+    private final LauncherAccessibilityDelegate mAccessibilityDelegate;
     private final boolean mIsRtl;
 
-    public ShortcutsItemView mShortcutsItemView;
+    private final int mArrayOffset;
+    private final View mArrow;
+
+    private BubbleTextView mOriginalIcon;
     private NotificationItemView mNotificationItemView;
 
-    protected BubbleTextView mOriginalIcon;
-    private final Rect mTempRect = new Rect();
-    private PointF mInterceptTouchDown = new PointF();
+    private ViewGroup mSystemShortcutContainer;
+
     private boolean mIsLeftAligned;
     protected boolean mIsAboveIcon;
-    private View mArrow;
+    private int mNumNotifications;
     private int mGravity;
 
     protected Animator mOpenCloseAnimator;
-    private boolean mDeferContainerRemoval;
-    private AnimatorSet mReduceHeightAnimatorSet;
+    protected boolean mDeferContainerRemoval;
     private final Rect mStartRect = new Rect();
     private final Rect mEndRect = new Rect();
 
     public PopupContainerWithArrow(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
-        mLauncher = Launcher.getLauncher(context);
-
         mStartDragThreshold = getResources().getDimensionPixelSize(
                 R.dimen.deep_shortcuts_start_drag_threshold);
+        mInflater = LayoutInflater.from(context);
+        mOutlineRadius = getResources().getDimension(R.dimen.bg_round_rect_radius);
+        mLauncher = Launcher.getLauncher(context);
         mAccessibilityDelegate = new ShortcutMenuAccessibilityDelegate(mLauncher);
         mIsRtl = Utilities.isRtl(getResources());
+
+        setClipToOutline(true);
+        setOutlineProvider(new ViewOutlineProvider() {
+            @Override
+            public void getOutline(View view, Outline outline) {
+                outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mOutlineRadius);
+            }
+        });
+
+        // Initialize arrow view
+        final Resources resources = getResources();
+        final int arrowWidth = resources.getDimensionPixelSize(R.dimen.popup_arrow_width);
+        final int arrowHeight = resources.getDimensionPixelSize(R.dimen.popup_arrow_height);
+        mArrow = new View(context);
+        mArrow.setLayoutParams(new DragLayer.LayoutParams(arrowWidth, arrowHeight));
+        mArrayOffset = resources.getDimensionPixelSize(R.dimen.popup_arrow_vertical_offset);
     }
 
     public PopupContainerWithArrow(Context context, AttributeSet attrs) {
@@ -147,6 +169,71 @@
         return mAccessibilityDelegate;
     }
 
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+            mInterceptTouchDown.set(ev.getX(), ev.getY());
+        }
+        if (mNotificationItemView != null
+                && mNotificationItemView.onInterceptTouchEvent(ev)) {
+            return true;
+        }
+        // Stop sending touch events to deep shortcut views if user moved beyond touch slop.
+        return Math.hypot(mInterceptTouchDown.x - ev.getX(), mInterceptTouchDown.y - ev.getY())
+                > ViewConfiguration.get(getContext()).getScaledTouchSlop();
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        if (mNotificationItemView != null) {
+            return mNotificationItemView.onTouchEvent(ev);
+        }
+        return super.onTouchEvent(ev);
+    }
+
+    @Override
+    protected boolean isOfType(int type) {
+        return (type & TYPE_ACTION_POPUP) != 0;
+    }
+
+    @Override
+    public void logActionCommand(int command) {
+        mLauncher.getUserEventDispatcher().logActionCommand(
+                command, mOriginalIcon, ContainerType.DEEPSHORTCUTS);
+    }
+
+    @Override
+    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+            DragLayer dl = mLauncher.getDragLayer();
+            if (!dl.isEventOverView(this, ev)) {
+                mLauncher.getUserEventDispatcher().logActionTapOutside(
+                        LoggerUtils.newContainerTarget(ContainerType.DEEPSHORTCUTS));
+                close(true);
+
+                // We let touches on the original icon go through so that users can launch
+                // the app with one tap if they don't find a shortcut they want.
+                return mOriginalIcon == null || !dl.isEventOverView(mOriginalIcon, ev);
+            }
+        }
+        return false;
+    }
+
+    @Override
+    protected void handleClose(boolean animate) {
+        if (animate) {
+            animateClose();
+        } else {
+            closeComplete();
+        }
+    }
+
+    public  <T extends View> T inflateAndAdd(int resId) {
+        View view = mInflater.inflate(resId, this, false);
+        addView(view);
+        return (T) view;
+    }
+
     /**
      * Shows the notifications and deep shortcuts associated with {@param icon}.
      * @return the container if shown or null.
@@ -173,271 +260,144 @@
         final PopupContainerWithArrow container =
                 (PopupContainerWithArrow) launcher.getLayoutInflater().inflate(
                         R.layout.popup_container, launcher.getDragLayer(), false);
-        container.setVisibility(View.INVISIBLE);
-        launcher.getDragLayer().addView(container);
         container.populateAndShow(icon, shortcutIds, notificationKeys, systemShortcuts);
         return container;
     }
 
-    public void populateAndShow(final BubbleTextView originalIcon, final List<String> shortcutIds,
+    private void populateAndShow(final BubbleTextView originalIcon, final List<String> shortcutIds,
             final List<NotificationKeyData> notificationKeys, List<SystemShortcut> systemShortcuts) {
-        final Resources resources = getResources();
-        final int arrowWidth = resources.getDimensionPixelSize(R.dimen.popup_arrow_width);
-        final int arrowHeight = resources.getDimensionPixelSize(R.dimen.popup_arrow_height);
-        final int arrowVerticalOffset = resources.getDimensionPixelSize(
-                R.dimen.popup_arrow_vertical_offset);
+        mNumNotifications = notificationKeys.size();
+
+        setVisibility(View.INVISIBLE);
+        mLauncher.getDragLayer().addView(this);
 
         mOriginalIcon = originalIcon;
 
-        // Add dummy views first, and populate with real info when ready.
-        PopupPopulator.Item[] itemsToPopulate = PopupPopulator
-                .getItemsToPopulate(shortcutIds, notificationKeys, systemShortcuts);
-        addDummyViews(itemsToPopulate, notificationKeys.size());
+        // Add views
+        if (mNumNotifications > 0) {
+            // Add notification entries
+            View.inflate(getContext(), R.layout.notification_content, this);
+            mNotificationItemView = new NotificationItemView(this);
+            if (mNumNotifications == 1) {
+                mNotificationItemView.removeFooter();
+            }
+            updateNotificationHeader();
+        }
+        int viewsToFlip = getChildCount();
+        mSystemShortcutContainer = this;
 
-        measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
-        orientAboutIcon(originalIcon, arrowHeight + arrowVerticalOffset);
+        if (!shortcutIds.isEmpty()) {
+            if (mNotificationItemView != null) {
+                mNotificationItemView.addGutter();
+            }
+
+            for (int i = shortcutIds.size(); i > 0; i--) {
+                mShortcuts.add(inflateAndAdd(R.layout.deep_shortcut));
+            }
+            updateHiddenShortcuts();
+
+            if (!systemShortcuts.isEmpty()) {
+                mSystemShortcutContainer = inflateAndAdd(R.layout.system_shortcut_icons);
+                for (SystemShortcut shortcut : systemShortcuts) {
+                    View view = mInflater.inflate(R.layout.system_shortcut_icon_only,
+                            mSystemShortcutContainer, false);
+                    mSystemShortcutContainer.addView(view);
+                    initializeSystemShortcut(view, shortcut);
+                }
+            }
+        } else if (!systemShortcuts.isEmpty()) {
+            if (mNotificationItemView != null) {
+                mNotificationItemView.addGutter();
+            }
+
+            for (SystemShortcut shortcut : systemShortcuts) {
+                initializeSystemShortcut(inflateAndAdd(R.layout.system_shortcut), shortcut);
+            }
+        }
+        orientAboutIcon();
 
         boolean reverseOrder = mIsAboveIcon;
         if (reverseOrder) {
+            int count = getChildCount();
+            ArrayList<View> allViews = new ArrayList<>(count);
+            for (int i = 0; i < count; i++) {
+                if (i == viewsToFlip) {
+                    Collections.reverse(allViews);
+                }
+                allViews.add(getChildAt(i));
+            }
+            Collections.reverse(allViews);
             removeAllViews();
-            mNotificationItemView = null;
-            mShortcutsItemView = null;
-            itemsToPopulate = PopupPopulator.reverseItems(itemsToPopulate);
-            addDummyViews(itemsToPopulate, notificationKeys.size());
+            for (int i = 0; i < count; i++) {
+                addView(allViews.get(i));
+            }
+            if (mNotificationItemView != null) {
+                mNotificationItemView.inverseGutterMargin();
+            }
 
-            measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
-            orientAboutIcon(originalIcon, arrowHeight + arrowVerticalOffset);
+            orientAboutIcon();
         }
+        updateDividers();
+
+        // Add the arrow.
+        final int arrowHorizontalOffset = getResources().getDimensionPixelSize(isAlignedWithStart()
+                ? R.dimen.popup_arrow_horizontal_offset_start
+                : R.dimen.popup_arrow_horizontal_offset_end);
+        mLauncher.getDragLayer().addView(mArrow);
+        DragLayer.LayoutParams arrowLp = (DragLayer.LayoutParams) mArrow.getLayoutParams();
+        if (mIsLeftAligned) {
+            mArrow.setX(getX() + arrowHorizontalOffset);
+        } else {
+            mArrow.setX(getX() + getMeasuredWidth() - arrowHorizontalOffset);
+        }
+
+        if (Gravity.isVertical(mGravity)) {
+            // This is only true if there wasn't room for the container next to the icon,
+            // so we centered it instead. In that case we don't want to show the arrow.
+            mArrow.setVisibility(INVISIBLE);
+        } else {
+            ShapeDrawable arrowDrawable = new ShapeDrawable(TriangleShape.create(
+                    arrowLp.width, arrowLp.height, !mIsAboveIcon));
+            Paint arrowPaint = arrowDrawable.getPaint();
+            arrowPaint.setColor(Themes.getAttrColor(mLauncher, R.attr.popupColorPrimary));
+            // The corner path effect won't be reflected in the shadow, but shouldn't be noticeable.
+            int radius = getResources().getDimensionPixelSize(R.dimen.popup_arrow_corner_radius);
+            arrowPaint.setPathEffect(new CornerPathEffect(radius));
+            mArrow.setBackground(arrowDrawable);
+            mArrow.setElevation(getElevation());
+        }
+
+        mArrow.setPivotX(arrowLp.width / 2);
+        mArrow.setPivotY(mIsAboveIcon ? 0 : arrowLp.height);
+
+        animateOpen();
 
         ItemInfo originalItemInfo = (ItemInfo) originalIcon.getTag();
-        List<DeepShortcutView> shortcutViews = mShortcutsItemView == null
-                ? Collections.EMPTY_LIST
-                : mShortcutsItemView.getDeepShortcutViews(reverseOrder);
-        List<View> systemShortcutViews = mShortcutsItemView == null
-                ? Collections.EMPTY_LIST
-                : mShortcutsItemView.getSystemShortcutViews(reverseOrder);
-        if (mNotificationItemView != null) {
-            updateNotificationHeader();
-        }
-
-        int numShortcuts = shortcutViews.size() + systemShortcutViews.size();
-        int numNotifications = notificationKeys.size();
-        if (numNotifications == 0) {
+        int numShortcuts = mShortcuts.size() + systemShortcuts.size();
+        if (mNumNotifications == 0) {
             setContentDescription(getContext().getString(R.string.shortcuts_menu_description,
                     numShortcuts, originalIcon.getContentDescription().toString()));
         } else {
             setContentDescription(getContext().getString(
                     R.string.shortcuts_menu_with_notifications_description, numShortcuts,
-                    numNotifications, originalIcon.getContentDescription().toString()));
+                    mNumNotifications, originalIcon.getContentDescription().toString()));
         }
 
-        // Add the arrow.
-        final int arrowHorizontalOffset = resources.getDimensionPixelSize(isAlignedWithStart() ?
-                R.dimen.popup_arrow_horizontal_offset_start :
-                R.dimen.popup_arrow_horizontal_offset_end);
-        mArrow = addArrowView(arrowHorizontalOffset, arrowVerticalOffset, arrowWidth, arrowHeight);
-        mArrow.setPivotX(arrowWidth / 2);
-        mArrow.setPivotY(mIsAboveIcon ? 0 : arrowHeight);
-
-        measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
-        animateOpen();
-
         mLauncher.getDragController().addDragListener(this);
         mOriginalIcon.forceHideBadge(true);
 
+        // All views are added. Animate layout from now on.
+        setLayoutTransition(new LayoutTransition());
+
         // Load the shortcuts on a background thread and update the container as it animates.
         final Looper workerLooper = LauncherModel.getWorkerLooper();
         new Handler(workerLooper).postAtFrontOfQueue(PopupPopulator.createUpdateRunnable(
                 mLauncher, originalItemInfo, new Handler(Looper.getMainLooper()),
-                this, shortcutIds, shortcutViews, notificationKeys, mNotificationItemView,
-                systemShortcuts, systemShortcutViews));
+                this, shortcutIds, mShortcuts, notificationKeys));
     }
 
-    private void addDummyViews(PopupPopulator.Item[] itemTypesToPopulate, int numNotifications) {
-        final Resources res = getResources();
-        final LayoutInflater inflater = mLauncher.getLayoutInflater();
-
-        int shortcutsItemRoundedCorners = ROUNDED_TOP_CORNERS | ROUNDED_BOTTOM_CORNERS;
-        int numItems = itemTypesToPopulate.length;
-        for (int i = 0; i < numItems; i++) {
-            PopupPopulator.Item itemTypeToPopulate = itemTypesToPopulate[i];
-            PopupPopulator.Item prevItemTypeToPopulate =
-                    i > 0 ? itemTypesToPopulate[i - 1] : null;
-            PopupPopulator.Item nextItemTypeToPopulate =
-                    i < numItems - 1 ? itemTypesToPopulate[i + 1] : null;
-            final View item = inflater.inflate(itemTypeToPopulate.layoutId, this, false);
-
-            boolean shouldUnroundTopCorners = prevItemTypeToPopulate != null
-                    && itemTypeToPopulate.isShortcut ^ prevItemTypeToPopulate.isShortcut;
-            boolean shouldUnroundBottomCorners = nextItemTypeToPopulate != null
-                    && itemTypeToPopulate.isShortcut ^ nextItemTypeToPopulate.isShortcut;
-
-            if (itemTypeToPopulate == PopupPopulator.Item.NOTIFICATION) {
-                mNotificationItemView = (NotificationItemView) item;
-                boolean notificationFooterHasIcons = numNotifications > 1;
-                int footerHeight = res.getDimensionPixelSize(
-                        notificationFooterHasIcons ? R.dimen.notification_footer_height
-                                : R.dimen.notification_empty_footer_height);
-                item.findViewById(R.id.footer).getLayoutParams().height = footerHeight;
-                if (notificationFooterHasIcons) {
-                    mNotificationItemView.findViewById(R.id.divider).setVisibility(VISIBLE);
-                }
-
-                int roundedCorners = ROUNDED_TOP_CORNERS | ROUNDED_BOTTOM_CORNERS;
-                if (shouldUnroundTopCorners) {
-                    roundedCorners &= ~ROUNDED_TOP_CORNERS;
-                    mNotificationItemView.findViewById(R.id.gutter_top).setVisibility(VISIBLE);
-                }
-                if (shouldUnroundBottomCorners) {
-                    roundedCorners &= ~ROUNDED_BOTTOM_CORNERS;
-                    mNotificationItemView.findViewById(R.id.gutter_bottom).setVisibility(VISIBLE);
-                }
-                int backgroundColor = Themes.getAttrColor(mLauncher, R.attr.popupColorTertiary);
-                mNotificationItemView.setBackgroundWithCorners(backgroundColor, roundedCorners);
-
-                mNotificationItemView.getMainView().setAccessibilityDelegate(mAccessibilityDelegate);
-            } else if (itemTypeToPopulate == PopupPopulator.Item.SHORTCUT) {
-                item.setAccessibilityDelegate(mAccessibilityDelegate);
-            }
-
-            if (itemTypeToPopulate.isShortcut) {
-                if (mShortcutsItemView == null) {
-                    mShortcutsItemView = (ShortcutsItemView) inflater.inflate(
-                            R.layout.shortcuts_item, this, false);
-                    addView(mShortcutsItemView);
-                    if (shouldUnroundTopCorners) {
-                        shortcutsItemRoundedCorners &= ~ROUNDED_TOP_CORNERS;
-                    }
-                }
-                if (itemTypeToPopulate != PopupPopulator.Item.SYSTEM_SHORTCUT_ICON
-                        && numNotifications > 0) {
-                    int prevHeight = item.getLayoutParams().height;
-                    // Condense shortcuts height when there are notifications.
-                    item.getLayoutParams().height = res.getDimensionPixelSize(
-                            R.dimen.bg_popup_item_condensed_height);
-                    if (item instanceof DeepShortcutView) {
-                        float iconScale = (float) item.getLayoutParams().height / prevHeight;
-                        ((DeepShortcutView) item).getIconView().setScaleX(iconScale);
-                        ((DeepShortcutView) item).getIconView().setScaleY(iconScale);
-                    }
-                }
-                mShortcutsItemView.addShortcutView(item, itemTypeToPopulate);
-                if (shouldUnroundBottomCorners) {
-                    shortcutsItemRoundedCorners &= ~ROUNDED_BOTTOM_CORNERS;
-                }
-            } else {
-                addView(item);
-            }
-        }
-        int backgroundColor = Themes.getAttrColor(mLauncher, R.attr.popupColorPrimary);
-        mShortcutsItemView.setBackgroundWithCorners(backgroundColor, shortcutsItemRoundedCorners);
-        if (numNotifications > 0) {
-            mShortcutsItemView.hideShortcuts(mIsAboveIcon, MAX_SHORTCUTS_IF_NOTIFICATIONS);
-        }
-    }
-
-    protected PopupItemView getItemViewAt(int index) {
-        if (!mIsAboveIcon) {
-            // Opening down, so arrow is the first view.
-            index++;
-        }
-        return (PopupItemView) getChildAt(index);
-    }
-
-    protected int getItemCount() {
-        // All children except the arrow are items.
-        return getChildCount() - 1;
-    }
-
-    private void animateOpen() {
-        setVisibility(View.VISIBLE);
-        mIsOpen = true;
-
-        final AnimatorSet openAnim = LauncherAnimUtils.createAnimatorSet();
-        final Resources res = getResources();
-        final long revealDuration = (long) res.getInteger(R.integer.config_popupOpenCloseDuration);
-        final TimeInterpolator revealInterpolator = new AccelerateDecelerateInterpolator();
-
-        // Rectangular reveal.
-        int itemsTotalHeight = 0;
-        for (int i = 0; i < getItemCount(); i++) {
-            itemsTotalHeight += getItemViewAt(i).getMeasuredHeight();
-        }
-        Point startPoint = computeAnimStartPoint(itemsTotalHeight);
-        int top = mIsAboveIcon ? getPaddingTop() : startPoint.y;
-        float radius = getItemViewAt(0).getBackgroundRadius();
-        mStartRect.set(startPoint.x, startPoint.y, startPoint.x, startPoint.y);
-        mEndRect.set(0, top, getMeasuredWidth(), top + itemsTotalHeight);
-        final ValueAnimator revealAnim = new RoundedRectRevealOutlineProvider
-                (radius, radius, mStartRect, mEndRect).createRevealAnimator(this, false);
-        revealAnim.setDuration(revealDuration);
-        revealAnim.setInterpolator(revealInterpolator);
-
-        Animator fadeIn = ObjectAnimator.ofFloat(this, ALPHA, 0, 1);
-        fadeIn.setDuration(revealDuration);
-        fadeIn.setInterpolator(revealInterpolator);
-        openAnim.play(fadeIn);
-
-        // Animate the arrow.
-        mArrow.setScaleX(0);
-        mArrow.setScaleY(0);
-        Animator arrowScale = createArrowScaleAnim(1).setDuration(res.getInteger(
-                R.integer.config_popupArrowOpenDuration));
-
-        openAnim.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mOpenCloseAnimator = null;
-                Utilities.sendCustomAccessibilityEvent(
-                        PopupContainerWithArrow.this,
-                        AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
-                        getContext().getString(R.string.action_deep_shortcut));
-            }
-        });
-
-        mOpenCloseAnimator = openAnim;
-        openAnim.playSequentially(revealAnim, arrowScale);
-        openAnim.start();
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int l, int t, int r, int b) {
-        super.onLayout(changed, l, t, r, b);
-        enforceContainedWithinScreen(l, r);
-
-    }
-
-    private void enforceContainedWithinScreen(int left, int right) {
-        DragLayer dragLayer = mLauncher.getDragLayer();
-        if (getTranslationX() + left < 0 ||
-                getTranslationX() + right > dragLayer.getWidth()) {
-            // If we are still off screen, center horizontally too.
-            mGravity |= Gravity.CENTER_HORIZONTAL;
-        }
-
-        if (Gravity.isHorizontal(mGravity)) {
-            setX(dragLayer.getWidth() / 2 - getMeasuredWidth() / 2);
-        }
-        if (Gravity.isVertical(mGravity)) {
-            setY(dragLayer.getHeight() / 2 - getMeasuredHeight() / 2);
-        }
-    }
-
-    /**
-     * Returns the point at which the center of the arrow merges with the first popup item.
-     */
-    private Point computeAnimStartPoint(int itemsTotalHeight) {
-        int arrowCenterX = getResources().getDimensionPixelSize(mIsLeftAligned ^ mIsRtl ?
-                R.dimen.popup_arrow_horizontal_center_start:
-                R.dimen.popup_arrow_horizontal_center_end);
-        if (!mIsLeftAligned) {
-            arrowCenterX = getMeasuredWidth() - arrowCenterX;
-        }
-        int arrowHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom()
-                - itemsTotalHeight;
-        // The y-coordinate of edge between the arrow and the first popup item.
-        int arrowEdge = getPaddingTop() + (mIsAboveIcon ? itemsTotalHeight : arrowHeight);
-        return new Point(arrowCenterX, arrowEdge);
+    protected boolean isAlignedWithStart() {
+        return mIsLeftAligned && !mIsRtl || !mIsLeftAligned && mIsRtl;
     }
 
     /**
@@ -452,17 +412,20 @@
      * So we always align left if there is enough horizontal space
      * and align above if there is enough vertical space.
      */
-    private void orientAboutIcon(BubbleTextView icon, int arrowHeight) {
+    protected void orientAboutIcon() {
+        measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
         int width = getMeasuredWidth();
-        int height = getMeasuredHeight() + arrowHeight;
+        int extraVerticalSpace = mArrow.getLayoutParams().height + mArrayOffset
+                + getResources().getDimensionPixelSize(R.dimen.popup_vertical_padding);
+        int height = getMeasuredHeight() + extraVerticalSpace;
 
         DragLayer dragLayer = mLauncher.getDragLayer();
-        dragLayer.getDescendantRectRelativeToSelf(icon, mTempRect);
+        dragLayer.getDescendantRectRelativeToSelf(mOriginalIcon, mTempRect);
         Rect insets = dragLayer.getInsets();
 
         // Align left (right in RTL) if there is room.
-        int leftAlignedX = mTempRect.left + icon.getPaddingLeft();
-        int rightAlignedX = mTempRect.right - width - icon.getPaddingRight();
+        int leftAlignedX = mTempRect.left + mOriginalIcon.getPaddingLeft();
+        int rightAlignedX = mTempRect.right - width - mOriginalIcon.getPaddingRight();
         int x = leftAlignedX;
         boolean canBeLeftAligned = leftAlignedX + width + insets.left
                 < dragLayer.getRight() - insets.right;
@@ -476,8 +439,9 @@
         }
 
         // Offset x so that the arrow and shortcut icons are center-aligned with the original icon.
-        int iconWidth = icon.getWidth() - icon.getTotalPaddingLeft() - icon.getTotalPaddingRight();
-        iconWidth *= icon.getScaleX();
+        int iconWidth = mOriginalIcon.getWidth()
+                - mOriginalIcon.getTotalPaddingLeft() - mOriginalIcon.getTotalPaddingRight();
+        iconWidth *= mOriginalIcon.getScaleX();
         Resources resources = getResources();
         int xOffset;
         if (isAlignedWithStart()) {
@@ -497,13 +461,11 @@
         x += mIsLeftAligned ? xOffset : -xOffset;
 
         // Open above icon if there is room.
-        int iconHeight = icon.getIcon() != null
-                ? icon.getIcon().getBounds().height()
-                : icon.getHeight();
-        int y = mTempRect.top + icon.getPaddingTop() - height;
+        int iconHeight = getIconHeightForPopupPlacement();
+        int y = mTempRect.top + mOriginalIcon.getPaddingTop() - height;
         mIsAboveIcon = y > dragLayer.getTop() + insets.top;
         if (!mIsAboveIcon) {
-            y = mTempRect.top + icon.getPaddingTop() + iconHeight;
+            y = mTempRect.top + mOriginalIcon.getPaddingTop() + iconHeight + extraVerticalSpace;
         }
 
         // Insets are added later, so subtract them now.
@@ -542,60 +504,187 @@
         }
 
         setX(x);
-        setY(y);
-    }
-
-    private boolean isAlignedWithStart() {
-        return mIsLeftAligned && !mIsRtl || !mIsLeftAligned && mIsRtl;
-    }
-
-    /**
-     * Adds an arrow view pointing at the original icon.
-     * @param horizontalOffset the horizontal offset of the arrow, so that it
-     *                              points at the center of the original icon
-     */
-    private View addArrowView(int horizontalOffset, int verticalOffset, int width, int height) {
-        LayoutParams layoutParams = new LayoutParams(width, height);
-        if (mIsLeftAligned) {
-            layoutParams.gravity = Gravity.LEFT;
-            layoutParams.leftMargin = horizontalOffset;
-        } else {
-            layoutParams.gravity = Gravity.RIGHT;
-            layoutParams.rightMargin = horizontalOffset;
-        }
-        if (mIsAboveIcon) {
-            layoutParams.topMargin = verticalOffset;
-        } else {
-            layoutParams.bottomMargin = verticalOffset;
-        }
-
-        View arrowView = new View(getContext());
         if (Gravity.isVertical(mGravity)) {
-            // This is only true if there wasn't room for the container next to the icon,
-            // so we centered it instead. In that case we don't want to show the arrow.
-            arrowView.setVisibility(INVISIBLE);
-        } else {
-            ShapeDrawable arrowDrawable = new ShapeDrawable(TriangleShape.create(
-                    width, height, !mIsAboveIcon));
-            Paint arrowPaint = arrowDrawable.getPaint();
-            // Note that we have to use getChildAt() instead of getItemViewAt(),
-            // since the latter expects the arrow which hasn't been added yet.
-            PopupItemView itemAttachedToArrow = (PopupItemView)
-                    (getChildAt(mIsAboveIcon ? getChildCount() - 1 : 0));
-            arrowPaint.setColor(Themes.getAttrColor(mLauncher, R.attr.popupColorPrimary));
-            // The corner path effect won't be reflected in the shadow, but shouldn't be noticeable.
-            int radius = getResources().getDimensionPixelSize(R.dimen.popup_arrow_corner_radius);
-            arrowPaint.setPathEffect(new CornerPathEffect(radius));
-            arrowView.setBackground(arrowDrawable);
-            arrowView.setElevation(getElevation());
+            return;
         }
-        addView(arrowView, mIsAboveIcon ? getChildCount() : 0, layoutParams);
-        return arrowView;
+
+        DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
+        DragLayer.LayoutParams arrowLp = (DragLayer.LayoutParams) mArrow.getLayoutParams();
+        if (mIsAboveIcon) {
+            arrowLp.gravity = lp.gravity = Gravity.BOTTOM;
+            lp.bottomMargin =
+                    mLauncher.getDragLayer().getHeight() - y - getMeasuredHeight() - insets.top;
+            arrowLp.bottomMargin = lp.bottomMargin - arrowLp.height - mArrayOffset - insets.bottom;
+        } else {
+            arrowLp.gravity = lp.gravity = Gravity.TOP;
+            lp.topMargin = y + insets.top;
+            arrowLp.topMargin = lp.topMargin - insets.top - arrowLp.height - mArrayOffset;
+        }
     }
 
     @Override
-    public View getExtendedTouchView() {
-        return mOriginalIcon;
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        super.onLayout(changed, l, t, r, b);
+
+        // enforce contained is within screen
+        DragLayer dragLayer = mLauncher.getDragLayer();
+        if (getTranslationX() + l < 0 ||
+                getTranslationX() + r > dragLayer.getWidth()) {
+            // If we are still off screen, center horizontally too.
+            mGravity |= Gravity.CENTER_HORIZONTAL;
+        }
+
+        if (Gravity.isHorizontal(mGravity)) {
+            setX(dragLayer.getWidth() / 2 - getMeasuredWidth() / 2);
+            mArrow.setVisibility(INVISIBLE);
+        }
+        if (Gravity.isVertical(mGravity)) {
+            setY(dragLayer.getHeight() / 2 - getMeasuredHeight() / 2);
+        }
+    }
+
+    protected void animateOpen() {
+        setVisibility(View.VISIBLE);
+        mIsOpen = true;
+
+        final AnimatorSet openAnim = LauncherAnimUtils.createAnimatorSet();
+        final Resources res = getResources();
+        final long revealDuration = (long) res.getInteger(R.integer.config_popupOpenCloseDuration);
+        final TimeInterpolator revealInterpolator = new AccelerateDecelerateInterpolator();
+
+        // Rectangular reveal.
+        final ValueAnimator revealAnim = createOpenCloseOutlineProvider()
+                .createRevealAnimator(this, false);
+        revealAnim.setDuration(revealDuration);
+        revealAnim.setInterpolator(revealInterpolator);
+
+        Animator fadeIn = ObjectAnimator.ofFloat(this, ALPHA, 0, 1);
+        fadeIn.setDuration(revealDuration);
+        fadeIn.setInterpolator(revealInterpolator);
+        openAnim.play(fadeIn);
+
+        // Animate the arrow.
+        mArrow.setScaleX(0);
+        mArrow.setScaleY(0);
+        Animator arrowScale = ObjectAnimator.ofFloat(mArrow, LauncherAnimUtils.SCALE_PROPERTY, 1)
+                .setDuration(res.getInteger(R.integer.config_popupArrowOpenDuration));
+
+        openAnim.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mOpenCloseAnimator = null;
+                Utilities.sendCustomAccessibilityEvent(
+                        PopupContainerWithArrow.this,
+                        AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
+                        getContext().getString(R.string.action_deep_shortcut));
+            }
+        });
+
+        mOpenCloseAnimator = openAnim;
+        openAnim.playSequentially(revealAnim, arrowScale);
+        openAnim.start();
+    }
+
+    public void applyNotificationInfos(List<NotificationInfo> notificationInfos) {
+        mNotificationItemView.applyNotificationInfos(notificationInfos);
+    }
+
+    private void updateHiddenShortcuts() {
+        int allowedCount = mNotificationItemView != null
+                ? MAX_SHORTCUTS_IF_NOTIFICATIONS : MAX_SHORTCUTS;
+        int originalHeight = getResources().getDimensionPixelSize(R.dimen.bg_popup_item_height);
+        int itemHeight = mNotificationItemView != null ?
+                getResources().getDimensionPixelSize(R.dimen.bg_popup_item_condensed_height)
+                : originalHeight;
+        float iconScale = ((float) itemHeight) / originalHeight;
+
+        int total = mShortcuts.size();
+        for (int i = 0; i < total; i++) {
+            DeepShortcutView view = mShortcuts.get(i);
+            view.setVisibility(i >= allowedCount ? GONE : VISIBLE);
+            view.getLayoutParams().height = itemHeight;
+            view.getIconView().setScaleX(iconScale);
+            view.getIconView().setScaleY(iconScale);
+        }
+    }
+
+    private void updateDividers() {
+        int count = getChildCount();
+        DeepShortcutView lastView = null;
+        for (int i = 0; i < count; i++) {
+            View view = getChildAt(i);
+            if (view.getVisibility() == VISIBLE && view instanceof DeepShortcutView) {
+                if (lastView != null) {
+                    lastView.setDividerVisibility(VISIBLE);
+                }
+                lastView = (DeepShortcutView) view;
+                lastView.setDividerVisibility(INVISIBLE);
+            }
+        }
+    }
+
+    @Override
+    protected void onWidgetsBound() {
+        ItemInfo itemInfo = (ItemInfo) mOriginalIcon.getTag();
+        SystemShortcut widgetInfo = new SystemShortcut.Widgets();
+        View.OnClickListener onClickListener = widgetInfo.getOnClickListener(mLauncher, itemInfo);
+        View widgetsView = null;
+        int count = mSystemShortcutContainer.getChildCount();
+        for (int i = 0; i < count; i++) {
+            View systemShortcutView = mSystemShortcutContainer.getChildAt(i);
+            if (systemShortcutView.getTag() instanceof SystemShortcut.Widgets) {
+                widgetsView = systemShortcutView;
+                break;
+            }
+        }
+
+        if (onClickListener != null && widgetsView == null) {
+            // We didn't have any widgets cached but now there are some, so enable the shortcut.
+            if (mSystemShortcutContainer != this) {
+                View view = mInflater.inflate(R.layout.system_shortcut_icon_only,
+                        mSystemShortcutContainer, false);
+                mSystemShortcutContainer.addView(view);
+                initializeSystemShortcut(view, widgetInfo);
+            } else {
+                // If using the expanded system shortcut (as opposed to just the icon), we need to
+                // reopen the container to ensure measurements etc. all work out. While this could
+                // be quite janky, in practice the user would typically see a small flicker as the
+                // animation restarts partway through, and this is a very rare edge case anyway.
+                ((PopupContainerWithArrow) getParent()).close(false);
+                PopupContainerWithArrow.showForIcon(mOriginalIcon);
+            }
+        } else if (onClickListener == null && widgetsView != null) {
+            // No widgets exist, but we previously added the shortcut so remove it.
+            if (mSystemShortcutContainer != this) {
+                mSystemShortcutContainer.removeView(widgetsView);
+            } else {
+                ((PopupContainerWithArrow) getParent()).close(false);
+                PopupContainerWithArrow.showForIcon(mOriginalIcon);
+            }
+        }
+    }
+
+    private void initializeSystemShortcut(View view, SystemShortcut info) {
+        if (view instanceof DeepShortcutView) {
+            // Expanded system shortcut, with both icon and text shown on white background.
+            final DeepShortcutView shortcutView = (DeepShortcutView) view;
+            shortcutView.getIconView().setBackgroundResource(info.iconResId);
+            shortcutView.getBubbleText().setText(info.labelResId);
+        } else if (view instanceof ImageView) {
+            // Only the system shortcut icon shows on a gray background header.
+            final ImageView shortcutIcon = (ImageView) view;
+            shortcutIcon.setImageResource(info.iconResId);
+            shortcutIcon.setContentDescription(getContext().getText(info.labelResId));
+        }
+        view.setTag(info);
+        view.setOnClickListener(info.getOnClickListener(mLauncher,
+                (ItemInfo) mOriginalIcon.getTag()));
+    }
+
+    protected int getIconHeightForPopupPlacement() {
+        return mOriginalIcon.getIcon() != null
+                ? mOriginalIcon.getIcon().getBounds().height()
+                : mOriginalIcon.getHeight();
     }
 
     /**
@@ -642,17 +731,6 @@
         };
     }
 
-    @Override
-    public boolean onInterceptTouchEvent(MotionEvent ev) {
-        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
-            mInterceptTouchDown.set(ev.getX(), ev.getY());
-            return false;
-        }
-        // Stop sending touch events to deep shortcut views if user moved beyond touch slop.
-        return Math.hypot(mInterceptTouchDown.x - ev.getX(), mInterceptTouchDown.y - ev.getY())
-                > ViewConfiguration.get(getContext()).getScaledTouchSlop();
-    }
-
     /**
      * Updates the notification header if the original icon's badge updated.
      */
@@ -680,146 +758,19 @@
         ItemInfo originalInfo = (ItemInfo) mOriginalIcon.getTag();
         BadgeInfo badgeInfo = updatedBadges.get(PackageUserKey.fromItemInfo(originalInfo));
         if (badgeInfo == null || badgeInfo.getNotificationKeys().size() == 0) {
-            // There are no more notifications, so create an animation to remove
-            // the notifications view and expand the shortcuts view (if possible).
-            AnimatorSet removeNotification = LauncherAnimUtils.createAnimatorSet();
-            int hiddenShortcutsHeight = 0;
-            if (mShortcutsItemView != null) {
-                hiddenShortcutsHeight = mShortcutsItemView.getHiddenShortcutsHeight();
-                int backgroundColor = Themes.getAttrColor(mLauncher, R.attr.popupColorPrimary);
-                // With notifications gone, all corners of shortcuts item should be rounded.
-                mShortcutsItemView.setBackgroundWithCorners(backgroundColor,
-                        ROUNDED_TOP_CORNERS | ROUNDED_BOTTOM_CORNERS);
-                removeNotification.play(mShortcutsItemView.showAllShortcuts(mIsAboveIcon));
-            }
-            final int duration = getResources().getInteger(
-                    R.integer.config_removeNotificationViewDuration);
-            removeNotification.play(adjustItemHeights(mNotificationItemView.getHeightMinusFooter(),
-                    hiddenShortcutsHeight, duration));
-            Animator fade = ObjectAnimator.ofFloat(mNotificationItemView, ALPHA, 0)
-                    .setDuration(duration);
-            fade.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    removeView(mNotificationItemView);
-                    mNotificationItemView = null;
-                    if (getItemCount() == 0) {
-                        close(false);
-                    }
-                }
-            });
-            removeNotification.play(fade);
-            final long arrowScaleDuration = getResources().getInteger(
-                    R.integer.config_popupArrowOpenDuration);
-            Animator hideArrow = createArrowScaleAnim(0).setDuration(arrowScaleDuration);
-            hideArrow.setStartDelay(0);
-            Animator showArrow = createArrowScaleAnim(1).setDuration(arrowScaleDuration);
-            showArrow.setStartDelay((long) (duration - arrowScaleDuration * 1.5));
-            removeNotification.playSequentially(hideArrow, showArrow);
-            removeNotification.start();
-            return;
+            // No more notifications, remove the notification views and expand all shortcuts.
+            mNotificationItemView.removeAllViews();
+            mNotificationItemView = null;
+            updateHiddenShortcuts();
+            updateDividers();
+        } else {
+            mNotificationItemView.trimNotifications(
+                    NotificationKeyData.extractKeysOnly(badgeInfo.getNotificationKeys()));
         }
-        mNotificationItemView.trimNotifications(NotificationKeyData.extractKeysOnly(
-                badgeInfo.getNotificationKeys()));
     }
 
     @Override
-    protected void onWidgetsBound() {
-        if (mShortcutsItemView != null) {
-            mShortcutsItemView.enableWidgetsIfExist(mOriginalIcon);
-        }
-    }
-
-    private ObjectAnimator createArrowScaleAnim(float scale) {
-        return LauncherAnimUtils.ofPropertyValuesHolder(
-                mArrow, new PropertyListBuilder().scale(scale).build());
-    }
-
-    public Animator reduceNotificationViewHeight(int heightToRemove, int duration) {
-        return adjustItemHeights(heightToRemove, 0, duration);
-    }
-
-    /**
-     * Animates the height of the notification item and the translationY of other items accordingly.
-     */
-    public Animator adjustItemHeights(int notificationHeightToRemove, int shortcutHeightToAdd,
-            int duration) {
-        if (mReduceHeightAnimatorSet != null) {
-            mReduceHeightAnimatorSet.cancel();
-        }
-        final int translateYBy = mIsAboveIcon ? notificationHeightToRemove - shortcutHeightToAdd
-                : -notificationHeightToRemove;
-        mReduceHeightAnimatorSet = LauncherAnimUtils.createAnimatorSet();
-        boolean removingNotification =
-                notificationHeightToRemove == mNotificationItemView.getHeightMinusFooter();
-        boolean shouldRemoveNotificationHeightFromTop = mIsAboveIcon && removingNotification;
-        mReduceHeightAnimatorSet.play(mNotificationItemView.animateHeightRemoval(
-                notificationHeightToRemove, shouldRemoveNotificationHeightFromTop));
-        PropertyResetListener<View, Float> resetTranslationYListener
-                = new PropertyResetListener<>(TRANSLATION_Y, 0f);
-        boolean itemIsAfterShortcuts = false;
-        for (int i = 0; i < getItemCount(); i++) {
-            final PopupItemView itemView = getItemViewAt(i);
-            if (itemIsAfterShortcuts) {
-                // Every item after the shortcuts item needs to adjust for the new height.
-                itemView.setTranslationY(itemView.getTranslationY() - shortcutHeightToAdd);
-            }
-            if (itemView == mNotificationItemView && (!mIsAboveIcon || removingNotification)) {
-                // The notification view is already in the right place.
-                continue;
-            }
-            ValueAnimator translateItem = ObjectAnimator.ofFloat(itemView, TRANSLATION_Y,
-                    itemView.getTranslationY() + translateYBy).setDuration(duration);
-            translateItem.addListener(resetTranslationYListener);
-            mReduceHeightAnimatorSet.play(translateItem);
-            if (itemView == mShortcutsItemView) {
-                itemIsAfterShortcuts = true;
-            }
-        }
-        if (mIsAboveIcon) {
-            // We also need to adjust the arrow position to account for the new shortcuts height.
-            mArrow.setTranslationY(mArrow.getTranslationY() - shortcutHeightToAdd);
-        }
-        mReduceHeightAnimatorSet.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                if (mIsAboveIcon) {
-                    // All the items, including the notification item, translated down, but the
-                    // container itself did not. This means the items would jump back to their
-                    // original translation unless we update the container's translationY here.
-                    setTranslationY(getTranslationY() + translateYBy);
-                    mArrow.setTranslationY(0);
-                }
-                mReduceHeightAnimatorSet = null;
-            }
-        });
-        return mReduceHeightAnimatorSet;
-    }
-
-    @Override
-    public boolean supportsAppInfoDropTarget() {
-        return true;
-    }
-
-    @Override
-    public boolean supportsDeleteDropTarget() {
-        return false;
-    }
-
-    @Override
-    public float getIntrinsicIconScaleFactor() {
-        return 1f;
-    }
-
-    @Override
-    public void onDropCompleted(View target, DropTarget.DragObject d, boolean isFlingToDelete,
-            boolean success) {
-        if (!success) {
-            d.dragView.remove();
-            mLauncher.showWorkspace(true);
-            mLauncher.getDropTargetBar().onDragEnd();
-        }
-    }
+    public void onDropCompleted(View target, DragObject d, boolean success) {  }
 
     @Override
     public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
@@ -846,17 +797,13 @@
 
     @Override
     public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
-        target.itemType = ItemType.DEEPSHORTCUT;
-        targetParent.containerType = ContainerType.DEEPSHORTCUTS;
-    }
-
-    @Override
-    protected void handleClose(boolean animate) {
-        if (animate) {
-            animateClose();
+        if (info == NOTIFICATION_ITEM_INFO) {
+            target.itemType = ItemType.NOTIFICATION;
         } else {
-            closeComplete();
+            target.itemType = ItemType.DEEPSHORTCUT;
+            target.rank = info.rank;
         }
+        targetParent.containerType = ContainerType.DEEPSHORTCUTS;
     }
 
     protected void animateClose() {
@@ -873,37 +820,27 @@
         mIsOpen = false;
 
         final AnimatorSet closeAnim = LauncherAnimUtils.createAnimatorSet();
+        // Hide the arrow
+        closeAnim.play(ObjectAnimator.ofFloat(mArrow, LauncherAnimUtils.SCALE_PROPERTY, 0));
+        closeAnim.play(ObjectAnimator.ofFloat(mArrow, ALPHA, 0));
+
+        // Animate original icon's text back in.
+        closeAnim.play(mOriginalIcon.createTextAlphaAnimator(true /* fadeIn */));
+        mOriginalIcon.forceHideBadge(false);
+
         final Resources res = getResources();
-        final long revealDuration = (long) res.getInteger(R.integer.config_popupOpenCloseDuration);
         final TimeInterpolator revealInterpolator = new AccelerateDecelerateInterpolator();
 
         // Rectangular reveal (reversed).
-        int itemsTotalHeight = 0;
-        for (int i = 0; i < getItemCount(); i++) {
-            itemsTotalHeight += getItemViewAt(i).getMeasuredHeight();
-        }
-        Point startPoint = computeAnimStartPoint(itemsTotalHeight);
-        int top = mIsAboveIcon ? getPaddingTop() : startPoint.y;
-        float radius = getItemViewAt(0).getBackgroundRadius();
-        mStartRect.set(startPoint.x, startPoint.y, startPoint.x, startPoint.y);
-        if (mEndRect.isEmpty()) {
-            mEndRect.set(0, top, getMeasuredWidth(), top + itemsTotalHeight);
-        }
-        final ValueAnimator revealAnim = new RoundedRectRevealOutlineProvider(
-                radius, radius, mStartRect, mEndRect).createRevealAnimator(this, true);
-        revealAnim.setDuration(revealDuration);
+        final ValueAnimator revealAnim = createOpenCloseOutlineProvider()
+                .createRevealAnimator(this, true);
         revealAnim.setInterpolator(revealInterpolator);
         closeAnim.play(revealAnim);
 
         Animator fadeOut = ObjectAnimator.ofFloat(this, ALPHA, 0);
-        fadeOut.setDuration(revealDuration);
         fadeOut.setInterpolator(revealInterpolator);
         closeAnim.play(fadeOut);
-
-        // Animate original icon's text back in.
-        Animator fadeText = mOriginalIcon.createTextAlphaAnimator(true /* fadeIn */);
-        fadeText.setDuration(revealDuration);
-        closeAnim.play(fadeText);
+        closeAnim.setDuration((long) res.getInteger(R.integer.config_popupOpenCloseDuration));
 
         closeAnim.addListener(new AnimatorListenerAdapter() {
             @Override
@@ -918,39 +855,88 @@
         });
         mOpenCloseAnimator = closeAnim;
         closeAnim.start();
-        mOriginalIcon.forceHideBadge(false);
+    }
+
+    private RoundedRectRevealOutlineProvider createOpenCloseOutlineProvider() {
+        int arrowCenterX = getResources().getDimensionPixelSize(mIsLeftAligned ^ mIsRtl ?
+                R.dimen.popup_arrow_horizontal_center_start:
+                R.dimen.popup_arrow_horizontal_center_end);
+        if (!mIsLeftAligned) {
+            arrowCenterX = getMeasuredWidth() - arrowCenterX;
+        }
+        int arrowCenterY = mIsAboveIcon ? getMeasuredHeight() : 0;
+
+        mStartRect.set(arrowCenterX, arrowCenterY, arrowCenterX, arrowCenterY);
+        if (mEndRect.isEmpty()) {
+            mEndRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
+        }
+
+        return new RoundedRectRevealOutlineProvider
+                (mOutlineRadius, mOutlineRadius, mStartRect, mEndRect);
     }
 
     /**
-     * Closes the folder without animation.
+     * Closes the popup without animation.
      */
-    protected void closeComplete() {
+    private void closeComplete() {
+        mOriginalIcon.setTextVisibility(mOriginalIcon.shouldTextBeVisible());
+        mOriginalIcon.forceHideBadge(false);
+
+        mLauncher.getDragController().removeDragListener(this);
         if (mOpenCloseAnimator != null) {
             mOpenCloseAnimator.cancel();
             mOpenCloseAnimator = null;
         }
         mIsOpen = false;
         mDeferContainerRemoval = false;
-        mOriginalIcon.setTextVisibility(mOriginalIcon.shouldTextBeVisible());
-        mOriginalIcon.forceHideBadge(false);
-        mLauncher.getDragController().removeDragListener(this);
         mLauncher.getDragLayer().removeView(this);
+        mLauncher.getDragLayer().removeView(mArrow);
     }
 
     @Override
-    protected boolean isOfType(int type) {
-        return (type & TYPE_POPUP_CONTAINER_WITH_ARROW) != 0;
+    public boolean onTouch(View v, MotionEvent ev) {
+        // Touched a shortcut, update where it was touched so we can drag from there on long click.
+        switch (ev.getAction()) {
+            case MotionEvent.ACTION_DOWN:
+            case MotionEvent.ACTION_MOVE:
+                mIconLastTouchPos.set((int) ev.getX(), (int) ev.getY());
+                break;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean onLongClick(View v) {
+        // Return early if not the correct view
+        if (!(v.getParent() instanceof DeepShortcutView)) return false;
+        // Return early if global dragging is not enabled
+        if (!mLauncher.isDraggingEnabled()) return false;
+        // Return early if an item is already being dragged (e.g. when long-pressing two shortcuts)
+        if (mLauncher.getDragController().isDragging()) return false;
+
+        // Long clicked on a shortcut.
+        DeepShortcutView sv = (DeepShortcutView) v.getParent();
+        sv.setWillDrawIcon(false);
+
+        // Move the icon to align with the center-top of the touch point
+        Point iconShift = new Point();
+        iconShift.x = mIconLastTouchPos.x - sv.getIconCenter().x;
+        iconShift.y = mIconLastTouchPos.y - mLauncher.getDeviceProfile().iconSizePx;
+
+        DragView dv = mLauncher.getWorkspace().beginDragShared(sv.getIconView(),
+                this, sv.getFinalInfo(),
+                new ShortcutDragPreviewProvider(sv.getIconView(), iconShift), new DragOptions());
+        dv.animateShift(-iconShift.x, -iconShift.y);
+
+        // TODO: support dragging from within folder without having to close it
+        AbstractFloatingView.closeOpenContainer(mLauncher, AbstractFloatingView.TYPE_FOLDER);
+        return false;
     }
 
     /**
-     * Returns a DeepShortcutsContainer which is already open or null
+     * Returns a PopupContainerWithArrow which is already open or null
      */
     public static PopupContainerWithArrow getOpen(Launcher launcher) {
-        return getOpenView(launcher, TYPE_POPUP_CONTAINER_WITH_ARROW);
-    }
-
-    @Override
-    public int getLogContainerType() {
-        return ContainerType.DEEPSHORTCUTS;
+        return getOpenView(launcher, TYPE_ACTION_POPUP);
     }
 }
diff --git a/src/com/android/launcher3/popup/PopupDataProvider.java b/src/com/android/launcher3/popup/PopupDataProvider.java
index de9f25e..abc186b 100644
--- a/src/com/android/launcher3/popup/PopupDataProvider.java
+++ b/src/com/android/launcher3/popup/PopupDataProvider.java
@@ -25,6 +25,8 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.badge.BadgeInfo;
+import com.android.launcher3.model.PackageItemInfo;
+import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.notification.NotificationInfo;
 import com.android.launcher3.notification.NotificationKeyData;
 import com.android.launcher3.notification.NotificationListener;
@@ -32,6 +34,7 @@
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.MultiHashMap;
 import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.widget.WidgetListRowEntry;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -53,6 +56,7 @@
     private static final SystemShortcut[] SYSTEM_SHORTCUTS = new SystemShortcut[] {
             new SystemShortcut.AppInfo(),
             new SystemShortcut.Widgets(),
+            new SystemShortcut.Install()
     };
 
     private final Launcher mLauncher;
@@ -61,6 +65,8 @@
     private MultiHashMap<ComponentKey, String> mDeepShortcutMap = new MultiHashMap<>();
     /** Maps packages to their BadgeInfo's . */
     private Map<PackageUserKey, BadgeInfo> mPackageUserToBadgeInfos = new HashMap<>();
+    /** Maps packages to their Widgets */
+    private ArrayList<WidgetListRowEntry> mAllWidgets = new ArrayList<>();
 
     public PopupDataProvider(Launcher launcher) {
         mLauncher = launcher;
@@ -101,11 +107,7 @@
                 mPackageUserToBadgeInfos.remove(removedPackageUserKey);
             }
             updateLauncherIconBadges(Utilities.singletonHashSet(removedPackageUserKey));
-
-            PopupContainerWithArrow openContainer = PopupContainerWithArrow.getOpen(mLauncher);
-            if (openContainer != null) {
-                openContainer.trimNotifications(mPackageUserToBadgeInfos);
-            }
+            trimNotifications(mPackageUserToBadgeInfos);
         }
     }
 
@@ -142,7 +144,10 @@
         if (!updatedBadges.isEmpty()) {
             updateLauncherIconBadges(updatedBadges.keySet());
         }
+        trimNotifications(updatedBadges);
+    }
 
+    private void trimNotifications(Map<PackageUserKey, BadgeInfo> updatedBadges) {
         PopupContainerWithArrow openContainer = PopupContainerWithArrow.getOpen(mLauncher);
         if (openContainer != null) {
             openContainer.trimNotifications(updatedBadges);
@@ -265,4 +270,29 @@
         }
         notificationListener.cancelNotification(notificationKey);
     }
+
+    public void setAllWidgets(ArrayList<WidgetListRowEntry> allWidgets) {
+        mAllWidgets = allWidgets;
+    }
+
+    public ArrayList<WidgetListRowEntry> getAllWidgets() {
+        return mAllWidgets;
+    }
+
+    public List<WidgetItem> getWidgetsForPackageUser(PackageUserKey packageUserKey) {
+        for (WidgetListRowEntry entry : mAllWidgets) {
+            if (entry.pkgItem.packageName.equals(packageUserKey.mPackageName)) {
+                ArrayList<WidgetItem> widgets = new ArrayList<>(entry.widgets);
+                // Remove widgets not associated with the correct user.
+                Iterator<WidgetItem> iterator = widgets.iterator();
+                while (iterator.hasNext()) {
+                    if (!iterator.next().user.equals(packageUserKey.mUser)) {
+                        iterator.remove();
+                    }
+                }
+                return widgets.isEmpty() ? null : widgets;
+            }
+        }
+        return null;
+    }
 }
diff --git a/src/com/android/launcher3/popup/PopupItemView.java b/src/com/android/launcher3/popup/PopupItemView.java
deleted file mode 100644
index 8ec051b..0000000
--- a/src/com/android/launcher3/popup/PopupItemView.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * 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.popup;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Matrix;
-import android.graphics.Paint;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffXfermode;
-import android.graphics.Rect;
-import android.graphics.drawable.ShapeDrawable;
-import android.graphics.drawable.shapes.RoundRectShape;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.FrameLayout;
-
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.popup.PopupContainerWithArrow.RoundedCornerFlags;
-
-import static com.android.launcher3.popup.PopupContainerWithArrow.ROUNDED_BOTTOM_CORNERS;
-import static com.android.launcher3.popup.PopupContainerWithArrow.ROUNDED_TOP_CORNERS;
-
-/**
- * An abstract {@link FrameLayout} that contains content for {@link PopupContainerWithArrow}.
- */
-public abstract class PopupItemView extends FrameLayout {
-
-    protected final Rect mPillRect;
-    protected  @RoundedCornerFlags int mRoundedCorners;
-    protected final boolean mIsRtl;
-    protected View mIconView;
-
-    private final Paint mBackgroundClipPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
-    private final Matrix mMatrix = new Matrix();
-    private Bitmap mRoundedCornerBitmap;
-
-    public PopupItemView(Context context) {
-        this(context, null, 0);
-    }
-
-    public PopupItemView(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public PopupItemView(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-
-        mPillRect = new Rect();
-
-        // Initialize corner clipping Bitmap and Paint.
-        int radius = (int) getBackgroundRadius();
-        mRoundedCornerBitmap = Bitmap.createBitmap(radius, radius, Bitmap.Config.ALPHA_8);
-        Canvas canvas = new Canvas();
-        canvas.setBitmap(mRoundedCornerBitmap);
-        canvas.drawArc(0, 0, radius*2, radius*2, 180, 90, true, mBackgroundClipPaint);
-        mBackgroundClipPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
-
-        mIsRtl = Utilities.isRtl(getResources());
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        mIconView = findViewById(R.id.popup_item_icon);
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-        mPillRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
-    }
-
-    @Override
-    protected void dispatchDraw(Canvas canvas) {
-        if (mRoundedCorners == 0) {
-            super.dispatchDraw(canvas);
-            return;
-        }
-
-        int saveCount = canvas.saveLayer(0, 0, getWidth(), getHeight(), null);
-        super.dispatchDraw(canvas);
-
-        // Clip children to this item's rounded corners.
-        int cornerWidth = mRoundedCornerBitmap.getWidth();
-        int cornerHeight = mRoundedCornerBitmap.getHeight();
-        int cornerCenterX = Math.round(cornerWidth / 2f);
-        int cornerCenterY = Math.round(cornerHeight / 2f);
-        if ((mRoundedCorners & ROUNDED_TOP_CORNERS) != 0) {
-            // Clip top left corner.
-            mMatrix.reset();
-            canvas.drawBitmap(mRoundedCornerBitmap, mMatrix, mBackgroundClipPaint);
-            // Clip top right corner.
-            mMatrix.setRotate(90, cornerCenterX, cornerCenterY);
-            mMatrix.postTranslate(canvas.getWidth() - cornerWidth, 0);
-            canvas.drawBitmap(mRoundedCornerBitmap, mMatrix, mBackgroundClipPaint);
-        }
-        if ((mRoundedCorners & ROUNDED_BOTTOM_CORNERS) != 0) {
-            // Clip bottom right corner.
-            mMatrix.setRotate(180, cornerCenterX, cornerCenterY);
-            mMatrix.postTranslate(canvas.getWidth() - cornerWidth, canvas.getHeight() - cornerHeight);
-            canvas.drawBitmap(mRoundedCornerBitmap, mMatrix, mBackgroundClipPaint);
-            // Clip bottom left corner.
-            mMatrix.setRotate(270, cornerCenterX, cornerCenterY);
-            mMatrix.postTranslate(0, canvas.getHeight() - cornerHeight);
-            canvas.drawBitmap(mRoundedCornerBitmap, mMatrix, mBackgroundClipPaint);
-        }
-
-        canvas.restoreToCount(saveCount);
-    }
-
-    /**
-     * Creates a round rect drawable (with the specified corners unrounded)
-     * and sets it as this View's background.
-     */
-    public void setBackgroundWithCorners(int color, @RoundedCornerFlags int roundedCorners) {
-        mRoundedCorners = roundedCorners;
-        float rTop = (roundedCorners & ROUNDED_TOP_CORNERS) == 0 ? 0 : getBackgroundRadius();
-        float rBot = (roundedCorners & ROUNDED_BOTTOM_CORNERS) == 0 ? 0 : getBackgroundRadius();
-        float[] radii = new float[] {rTop, rTop, rTop, rTop, rBot, rBot, rBot, rBot};
-        ShapeDrawable roundRectBackground = new ShapeDrawable(new RoundRectShape(radii, null, null));
-        roundRectBackground.getPaint().setColor(color);
-        setBackground(roundRectBackground);
-    }
-
-    protected float getBackgroundRadius() {
-        return getResources().getDimensionPixelSize(R.dimen.bg_round_rect_radius);
-    }
-}
diff --git a/src/com/android/launcher3/popup/PopupPopulator.java b/src/com/android/launcher3/popup/PopupPopulator.java
index 0dc1ca0..6c83d12 100644
--- a/src/com/android/launcher3/popup/PopupPopulator.java
+++ b/src/com/android/launcher3/popup/PopupPopulator.java
@@ -17,23 +17,17 @@
 package com.android.launcher3.popup;
 
 import android.content.ComponentName;
-import android.content.Context;
 import android.os.Handler;
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
-import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.annotation.VisibleForTesting;
-import android.view.View;
-import android.widget.ImageView;
 
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.R;
 import com.android.launcher3.ShortcutInfo;
 import com.android.launcher3.graphics.LauncherIcons;
 import com.android.launcher3.notification.NotificationInfo;
-import com.android.launcher3.notification.NotificationItemView;
 import com.android.launcher3.notification.NotificationKeyData;
 import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.shortcuts.DeepShortcutView;
@@ -56,55 +50,6 @@
     @VisibleForTesting static final int NUM_DYNAMIC = 2;
     public static final int MAX_SHORTCUTS_IF_NOTIFICATIONS = 2;
 
-    public enum Item {
-        SHORTCUT(R.layout.deep_shortcut, true),
-        NOTIFICATION(R.layout.notification, false),
-        SYSTEM_SHORTCUT(R.layout.system_shortcut, true),
-        SYSTEM_SHORTCUT_ICON(R.layout.system_shortcut_icon_only, true);
-
-        public final int layoutId;
-        public final boolean isShortcut;
-
-        Item(int layoutId, boolean isShortcut) {
-            this.layoutId = layoutId;
-            this.isShortcut = isShortcut;
-        }
-    }
-
-    public static @NonNull Item[] getItemsToPopulate(@NonNull List<String> shortcutIds,
-            @NonNull List<NotificationKeyData> notificationKeys,
-            @NonNull List<SystemShortcut> systemShortcuts) {
-        boolean hasNotifications = notificationKeys.size() > 0;
-        int numNotificationItems = hasNotifications ? 1 : 0;
-        int numShortcuts = shortcutIds.size();
-        int numItems = Math.min(MAX_SHORTCUTS, numShortcuts) + numNotificationItems
-                + systemShortcuts.size();
-        Item[] items = new Item[numItems];
-        for (int i = 0; i < numItems; i++) {
-            items[i] = Item.SHORTCUT;
-        }
-        if (hasNotifications) {
-            // The notification layout is always first.
-            items[0] = Item.NOTIFICATION;
-        }
-        // The system shortcuts are always last.
-        boolean iconsOnly = !shortcutIds.isEmpty();
-        for (int i = 0; i < systemShortcuts.size(); i++) {
-            items[numItems - 1 - i] = iconsOnly ? Item.SYSTEM_SHORTCUT_ICON : Item.SYSTEM_SHORTCUT;
-        }
-        return items;
-    }
-
-    public static Item[] reverseItems(Item[] items) {
-        if (items == null) return null;
-        int numItems = items.length;
-        Item[] reversedArray = new Item[numItems];
-        for (int i = 0; i < numItems; i++) {
-            reversedArray[i] = items[numItems - i - 1];
-        }
-        return reversedArray;
-    }
-
     /**
      * Sorts shortcuts in rank order, with manifest shortcuts coming before dynamic shortcuts.
      */
@@ -179,137 +124,42 @@
     public static Runnable createUpdateRunnable(final Launcher launcher, final ItemInfo originalInfo,
             final Handler uiHandler, final PopupContainerWithArrow container,
             final List<String> shortcutIds, final List<DeepShortcutView> shortcutViews,
-            final List<NotificationKeyData> notificationKeys,
-            final NotificationItemView notificationView, final List<SystemShortcut> systemShortcuts,
-            final List<View> systemShortcutViews) {
+            final List<NotificationKeyData> notificationKeys) {
         final ComponentName activity = originalInfo.getTargetComponent();
         final UserHandle user = originalInfo.user;
-        return new Runnable() {
-            @Override
-            public void run() {
-                if (notificationView != null) {
-                    List<StatusBarNotification> notifications = launcher.getPopupDataProvider()
-                            .getStatusBarNotificationsForKeys(notificationKeys);
-                    List<NotificationInfo> infos = new ArrayList<>(notifications.size());
-                    for (int i = 0; i < notifications.size(); i++) {
-                        StatusBarNotification notification = notifications.get(i);
-                        infos.add(new NotificationInfo(launcher, notification));
-                    }
-                    uiHandler.post(new UpdateNotificationChild(notificationView, infos));
+        return () -> {
+            if (!notificationKeys.isEmpty()) {
+                List<StatusBarNotification> notifications = launcher.getPopupDataProvider()
+                        .getStatusBarNotificationsForKeys(notificationKeys);
+                List<NotificationInfo> infos = new ArrayList<>(notifications.size());
+                for (int i = 0; i < notifications.size(); i++) {
+                    StatusBarNotification notification = notifications.get(i);
+                    infos.add(new NotificationInfo(launcher, notification));
                 }
-
-                List<ShortcutInfoCompat> shortcuts = DeepShortcutManager.getInstance(launcher)
-                        .queryForShortcutsContainer(activity, shortcutIds, user);
-                String shortcutIdToDeDupe = notificationKeys.isEmpty() ? null
-                        : notificationKeys.get(0).shortcutId;
-                shortcuts = PopupPopulator.sortAndFilterShortcuts(shortcuts, shortcutIdToDeDupe);
-                for (int i = 0; i < shortcuts.size() && i < shortcutViews.size(); i++) {
-                    final ShortcutInfoCompat shortcut = shortcuts.get(i);
-                    ShortcutInfo si = new ShortcutInfo(shortcut, launcher);
-                    // Use unbadged icon for the menu.
-                    si.iconBitmap = LauncherIcons.createShortcutIcon(
-                            shortcut, launcher, false /* badged */);
-                    si.rank = i;
-                    uiHandler.post(new UpdateShortcutChild(container, shortcutViews.get(i),
-                            si, shortcut));
-                }
-
-                // This ensures that mLauncher.getWidgetsForPackageUser()
-                // doesn't return null (it puts all the widgets in memory).
-                for (int i = 0; i < systemShortcuts.size(); i++) {
-                    final SystemShortcut systemShortcut = systemShortcuts.get(i);
-                    uiHandler.post(new UpdateSystemShortcutChild(container,
-                            systemShortcutViews.get(i), systemShortcut, launcher, originalInfo));
-                }
-                uiHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        launcher.refreshAndBindWidgetsForPackageUser(
-                                PackageUserKey.fromItemInfo(originalInfo));
-                    }
-                });
+                uiHandler.post(() -> container.applyNotificationInfos(infos));
             }
+
+            List<ShortcutInfoCompat> shortcuts = DeepShortcutManager.getInstance(launcher)
+                    .queryForShortcutsContainer(activity, shortcutIds, user);
+            String shortcutIdToDeDupe = notificationKeys.isEmpty() ? null
+                    : notificationKeys.get(0).shortcutId;
+            shortcuts = PopupPopulator.sortAndFilterShortcuts(shortcuts, shortcutIdToDeDupe);
+            for (int i = 0; i < shortcuts.size() && i < shortcutViews.size(); i++) {
+                final ShortcutInfoCompat shortcut = shortcuts.get(i);
+                final ShortcutInfo si = new ShortcutInfo(shortcut, launcher);
+                // Use unbadged icon for the menu.
+                si.iconBitmap = LauncherIcons.createShortcutIcon(
+                        shortcut, launcher, false /* badged */);
+                si.rank = i;
+
+                final DeepShortcutView view = shortcutViews.get(i);
+                uiHandler.post(() -> view.applyShortcutInfo(si, shortcut, container));
+            }
+
+            // This ensures that mLauncher.getWidgetsForPackageUser()
+            // doesn't return null (it puts all the widgets in memory).
+            uiHandler.post(() -> launcher.refreshAndBindWidgetsForPackageUser(
+                    PackageUserKey.fromItemInfo(originalInfo)));
         };
     }
-
-    /** Updates the shortcut child of this container based on the given shortcut info. */
-    private static class UpdateShortcutChild implements Runnable {
-        private final PopupContainerWithArrow mContainer;
-        private final DeepShortcutView mShortcutChild;
-        private final ShortcutInfo mShortcutChildInfo;
-        private final ShortcutInfoCompat mDetail;
-
-        public UpdateShortcutChild(PopupContainerWithArrow container, DeepShortcutView shortcutChild,
-                ShortcutInfo shortcutChildInfo, ShortcutInfoCompat detail) {
-            mContainer = container;
-            mShortcutChild = shortcutChild;
-            mShortcutChildInfo = shortcutChildInfo;
-            mDetail = detail;
-        }
-
-        @Override
-        public void run() {
-            mShortcutChild.applyShortcutInfo(mShortcutChildInfo, mDetail,
-                    mContainer.mShortcutsItemView);
-        }
-    }
-
-    /** Updates the notification child based on the given notification info. */
-    private static class UpdateNotificationChild implements Runnable {
-        private NotificationItemView mNotificationView;
-        private List<NotificationInfo> mNotificationInfos;
-
-        public UpdateNotificationChild(NotificationItemView notificationView,
-                List<NotificationInfo> notificationInfos) {
-            mNotificationView = notificationView;
-            mNotificationInfos = notificationInfos;
-        }
-
-        @Override
-        public void run() {
-            mNotificationView.applyNotificationInfos(mNotificationInfos);
-        }
-    }
-
-    /** Updates the system shortcut child based on the given shortcut info. */
-    private static class UpdateSystemShortcutChild implements Runnable {
-
-        private final PopupContainerWithArrow mContainer;
-        private final View mSystemShortcutChild;
-        private final SystemShortcut mSystemShortcutInfo;
-        private final Launcher mLauncher;
-        private final ItemInfo mItemInfo;
-
-        public UpdateSystemShortcutChild(PopupContainerWithArrow container, View systemShortcutChild,
-                SystemShortcut systemShortcut, Launcher launcher, ItemInfo originalInfo) {
-            mContainer = container;
-            mSystemShortcutChild = systemShortcutChild;
-            mSystemShortcutInfo = systemShortcut;
-            mLauncher = launcher;
-            mItemInfo = originalInfo;
-        }
-
-        @Override
-        public void run() {
-            final Context context = mSystemShortcutChild.getContext();
-            initializeSystemShortcut(context, mSystemShortcutChild, mSystemShortcutInfo);
-            mSystemShortcutChild.setOnClickListener(mSystemShortcutInfo
-                    .getOnClickListener(mLauncher, mItemInfo));
-        }
-    }
-
-    public static void initializeSystemShortcut(Context context, View view, SystemShortcut info) {
-        if (view instanceof DeepShortcutView) {
-            // Expanded system shortcut, with both icon and text shown on white background.
-            final DeepShortcutView shortcutView = (DeepShortcutView) view;
-            shortcutView.getIconView().setBackground(info.getIcon(context));
-            shortcutView.getBubbleText().setText(info.getLabel(context));
-        } else if (view instanceof ImageView) {
-            // Only the system shortcut icon shows on a gray background header.
-            final ImageView shortcutIcon = (ImageView) view;
-            shortcutIcon.setImageDrawable(info.getIcon(context));
-            shortcutIcon.setContentDescription(info.getLabel(context));
-        }
-        view.setTag(info);
-    }
 }
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index 6254d2d..c398aaa 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -1,8 +1,7 @@
 package com.android.launcher3.popup;
 
-import android.content.Context;
+import android.content.Intent;
 import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.view.View;
 
@@ -11,7 +10,10 @@
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
+import com.android.launcher3.ShortcutInfo;
 import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.util.InstantAppResolver;
+import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.widget.WidgetsBottomSheet;
 
@@ -27,20 +29,12 @@
  * Example system shortcuts, defined as inner classes, include Widgets and AppInfo.
  */
 public abstract class SystemShortcut extends ItemInfo {
-    private final int mIconResId;
-    private final int mLabelResId;
+    public final int iconResId;
+    public final int labelResId;
 
     public SystemShortcut(int iconResId, int labelResId) {
-        mIconResId = iconResId;
-        mLabelResId = labelResId;
-    }
-
-    public Drawable getIcon(Context context) {
-        return context.getResources().getDrawable(mIconResId, context.getTheme());
-    }
-
-    public String getLabel(Context context) {
-        return context.getString(mLabelResId);
+        this.iconResId = iconResId;
+        this.labelResId = labelResId;
     }
 
     public abstract View.OnClickListener getOnClickListener(final Launcher launcher,
@@ -55,8 +49,9 @@
         @Override
         public View.OnClickListener getOnClickListener(final Launcher launcher,
                 final ItemInfo itemInfo) {
-            final List<WidgetItem> widgets = launcher.getWidgetsForPackageUser(new PackageUserKey(
-                    itemInfo.getTargetComponent().getPackageName(), itemInfo.user));
+            final List<WidgetItem> widgets =
+                    launcher.getPopupDataProvider().getWidgetsForPackageUser(new PackageUserKey(
+                            itemInfo.getTargetComponent().getPackageName(), itemInfo.user));
             if (widgets == null) {
                 return null;
             }
@@ -88,11 +83,42 @@
                 public void onClick(View view) {
                     Rect sourceBounds = launcher.getViewBounds(view);
                     Bundle opts = launcher.getActivityLaunchOptions(view);
-                    InfoDropTarget.startDetailsActivityForInfo(itemInfo, launcher, null, sourceBounds, opts);
+                    InfoDropTarget.startDetailsActivityForInfo(itemInfo, launcher, sourceBounds, opts);
                     launcher.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP,
                             ControlType.APPINFO_TARGET, view);
                 }
             };
         }
     }
+
+    public static class Install extends SystemShortcut {
+        public Install() {
+            super(R.drawable.ic_install_no_shadow, R.string.install_drop_target_label);
+        }
+
+        @Override
+        public View.OnClickListener getOnClickListener(final Launcher launcher,
+                final ItemInfo itemInfo) {
+            boolean supportsWebUI = (itemInfo instanceof ShortcutInfo) &&
+                    ((ShortcutInfo) itemInfo).hasStatusFlag(ShortcutInfo.FLAG_SUPPORTS_WEB_UI);
+            boolean isInstantApp = false;
+            if (itemInfo instanceof com.android.launcher3.AppInfo) {
+                com.android.launcher3.AppInfo appInfo = (com.android.launcher3.AppInfo) itemInfo;
+                isInstantApp = InstantAppResolver.newInstance(launcher).isInstantApp(appInfo);
+            }
+            boolean enabled = supportsWebUI || isInstantApp;
+            if (!enabled) {
+                return null;
+            }
+            return new View.OnClickListener() {
+                @Override
+                public void onClick(View view) {
+                    Intent intent = PackageManagerHelper.getMarketIntent(itemInfo
+                            .getTargetComponent().getPackageName());
+                    launcher.startActivitySafely(view, intent, itemInfo);
+                    AbstractFloatingView.closeAllOpenViews(launcher);
+                }
+            };
+        }
+    }
 }
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutView.java b/src/com/android/launcher3/shortcuts/DeepShortcutView.java
index 75a4886..450a690 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutView.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutView.java
@@ -29,6 +29,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.ShortcutInfo;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.popup.PopupContainerWithArrow;
 
 /**
  * A {@link android.widget.FrameLayout} that contains a {@link DeepShortcutView}.
@@ -42,6 +43,7 @@
 
     private BubbleTextView mBubbleText;
     private View mIconView;
+    private View mDivider;
 
     private ShortcutInfo mInfo;
     private ShortcutInfoCompat mDetail;
@@ -65,6 +67,11 @@
         super.onFinishInflate();
         mBubbleText = findViewById(R.id.bubble_text);
         mIconView = findViewById(R.id.icon);
+        mDivider = findViewById(R.id.divider);
+    }
+
+    public void setDividerVisibility(int visibility) {
+        mDivider.setVisibility(visibility);
     }
 
     public BubbleTextView getBubbleText() {
@@ -98,7 +105,7 @@
 
     /** package private **/
     public void applyShortcutInfo(ShortcutInfo info, ShortcutInfoCompat detail,
-            ShortcutsItemView container) {
+            PopupContainerWithArrow container) {
         mInfo = info;
         mDetail = detail;
         mBubbleText.applyFromShortcutInfo(info);
diff --git a/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java b/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java
index e9d2b50..cfb9258 100644
--- a/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java
+++ b/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java
@@ -26,7 +26,6 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.graphics.DragPreviewProvider;
-import com.android.launcher3.graphics.HolographicOutlineHelper;
 
 /**
  * Extension of {@link DragPreviewProvider} which generates bitmaps scaled to the default icon size.
@@ -40,35 +39,17 @@
         mPositionShift = shift;
     }
 
-    @Override
-    public Bitmap createDragOutline(Canvas canvas) {
-        Bitmap b = drawScaledPreview(canvas, Bitmap.Config.ALPHA_8);
-
-        HolographicOutlineHelper.getInstance(mView.getContext())
-                .applyExpensiveOutlineWithBlur(b, canvas);
-        canvas.setBitmap(null);
-        return b;
-    }
-
-    @Override
-    public Bitmap createDragBitmap(Canvas canvas) {
-        Bitmap b = drawScaledPreview(canvas, Bitmap.Config.ARGB_8888);
-        canvas.setBitmap(null);
-        return b;
-    }
-
-    private Bitmap drawScaledPreview(Canvas canvas, Bitmap.Config config) {
+    public Bitmap createDragBitmap() {
         Drawable d = mView.getBackground();
         Rect bounds = getDrawableBounds(d);
 
         int size = Launcher.getLauncher(mView.getContext()).getDeviceProfile().iconSizePx;
-
         final Bitmap b = Bitmap.createBitmap(
                 size + blurSizeOutline,
                 size + blurSizeOutline,
-                config);
+                Bitmap.Config.ARGB_8888);
 
-        canvas.setBitmap(b);
+        Canvas canvas = new Canvas(b);
         canvas.save(Canvas.MATRIX_SAVE_FLAG);
         canvas.translate(blurSizeOutline / 2, blurSizeOutline / 2);
         canvas.scale(((float) size) / bounds.width(), ((float) size) / bounds.height(), 0, 0);
diff --git a/src/com/android/launcher3/shortcuts/ShortcutsItemView.java b/src/com/android/launcher3/shortcuts/ShortcutsItemView.java
deleted file mode 100644
index b4fa04e..0000000
--- a/src/com/android/launcher3/shortcuts/ShortcutsItemView.java
+++ /dev/null
@@ -1,340 +0,0 @@
-/*
- * 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.shortcuts;
-
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.content.Context;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.View;
-import android.widget.LinearLayout;
-
-import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAnimUtils;
-import com.android.launcher3.R;
-import com.android.launcher3.anim.PropertyListBuilder;
-import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
-import com.android.launcher3.dragndrop.DragOptions;
-import com.android.launcher3.dragndrop.DragView;
-import com.android.launcher3.logging.UserEventDispatcher.LogContainerProvider;
-import com.android.launcher3.popup.PopupContainerWithArrow;
-import com.android.launcher3.popup.PopupItemView;
-import com.android.launcher3.popup.PopupPopulator;
-import com.android.launcher3.popup.SystemShortcut;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * A {@link PopupItemView} that contains all of the {@link DeepShortcutView}s for an app,
- * as well as the system shortcuts such as Widgets and App Info.
- */
-public class ShortcutsItemView extends PopupItemView implements View.OnLongClickListener,
-        View.OnTouchListener, LogContainerProvider {
-
-    private static final String TAG = "ShortcutsItem";
-
-    private Launcher mLauncher;
-    private LinearLayout mContent;
-    private LinearLayout mShortcutsLayout;
-    private LinearLayout mSystemShortcutIcons;
-    private final Point mIconShift = new Point();
-    private final Point mIconLastTouchPos = new Point();
-    private final List<DeepShortcutView> mDeepShortcutViews = new ArrayList<>();
-    private final List<View> mSystemShortcutViews = new ArrayList<>();
-
-    private int mHiddenShortcutsHeight;
-
-    public ShortcutsItemView(Context context) {
-        this(context, null, 0);
-    }
-
-    public ShortcutsItemView(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public ShortcutsItemView(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-
-        mLauncher = Launcher.getLauncher(context);
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        mContent = findViewById(R.id.content);
-        mShortcutsLayout = findViewById(R.id.shortcuts);
-    }
-
-    @Override
-    public boolean onTouch(View v, MotionEvent ev) {
-        // Touched a shortcut, update where it was touched so we can drag from there on long click.
-        switch (ev.getAction()) {
-            case MotionEvent.ACTION_DOWN:
-            case MotionEvent.ACTION_MOVE:
-                mIconLastTouchPos.set((int) ev.getX(), (int) ev.getY());
-                break;
-        }
-        return false;
-    }
-
-    @Override
-    public boolean onLongClick(View v) {
-        // Return early if not the correct view
-        if (!(v.getParent() instanceof DeepShortcutView)) return false;
-        // Return early if global dragging is not enabled
-        if (!mLauncher.isDraggingEnabled()) return false;
-        // Return early if an item is already being dragged (e.g. when long-pressing two shortcuts)
-        if (mLauncher.getDragController().isDragging()) return false;
-
-        // Long clicked on a shortcut.
-        DeepShortcutView sv = (DeepShortcutView) v.getParent();
-        sv.setWillDrawIcon(false);
-
-        // Move the icon to align with the center-top of the touch point
-        mIconShift.x = mIconLastTouchPos.x - sv.getIconCenter().x;
-        mIconShift.y = mIconLastTouchPos.y - mLauncher.getDeviceProfile().iconSizePx;
-
-        DragView dv = mLauncher.getWorkspace().beginDragShared(sv.getIconView(),
-                (PopupContainerWithArrow) getParent(), sv.getFinalInfo(),
-                new ShortcutDragPreviewProvider(sv.getIconView(), mIconShift), new DragOptions());
-        dv.animateShift(-mIconShift.x, -mIconShift.y);
-
-        // TODO: support dragging from within folder without having to close it
-        AbstractFloatingView.closeOpenContainer(mLauncher, AbstractFloatingView.TYPE_FOLDER);
-        return false;
-    }
-
-    public void addShortcutView(View shortcutView, PopupPopulator.Item shortcutType) {
-        addShortcutView(shortcutView, shortcutType, -1);
-    }
-
-    private void addShortcutView(View shortcutView, PopupPopulator.Item shortcutType, int index) {
-        if (shortcutType == PopupPopulator.Item.SHORTCUT) {
-            mDeepShortcutViews.add((DeepShortcutView) shortcutView);
-        } else {
-            mSystemShortcutViews.add(shortcutView);
-        }
-        if (shortcutType == PopupPopulator.Item.SYSTEM_SHORTCUT_ICON) {
-            // System shortcut icons are added to a header that is separate from the full shortcuts.
-            if (mSystemShortcutIcons == null) {
-                mSystemShortcutIcons = (LinearLayout) mLauncher.getLayoutInflater().inflate(
-                        R.layout.system_shortcut_icons, mContent, false);
-                boolean iconsAreBelowShortcuts = mShortcutsLayout.getChildCount() > 0;
-                mContent.addView(mSystemShortcutIcons, iconsAreBelowShortcuts ? -1 : 0);
-            }
-            mSystemShortcutIcons.addView(shortcutView, index);
-        } else {
-            if (mShortcutsLayout.getChildCount() > 0) {
-                View prevChild = mShortcutsLayout.getChildAt(mShortcutsLayout.getChildCount() - 1);
-                if (prevChild instanceof DeepShortcutView) {
-                    prevChild.findViewById(R.id.divider).setVisibility(VISIBLE);
-                }
-            }
-            mShortcutsLayout.addView(shortcutView, index);
-        }
-    }
-
-    public List<DeepShortcutView> getDeepShortcutViews(boolean reverseOrder) {
-        if (reverseOrder) {
-            Collections.reverse(mDeepShortcutViews);
-        }
-        return mDeepShortcutViews;
-    }
-
-    public List<View> getSystemShortcutViews(boolean reverseOrder) {
-        // Always reverse system shortcut icons (in the header)
-        // so they are in priority order from right to left.
-        if (reverseOrder || mSystemShortcutIcons != null) {
-            Collections.reverse(mSystemShortcutViews);
-        }
-        return mSystemShortcutViews;
-    }
-
-    /**
-     * Hides shortcuts until only {@param maxShortcuts} are showing. Also sets
-     * {@link #mHiddenShortcutsHeight} to be the amount of extra space that shortcuts will
-     * require when {@link #showAllShortcuts(boolean)} is called.
-     */
-    public void hideShortcuts(boolean hideFromTop, int maxShortcuts) {
-        // When shortcuts are shown, they get more space allocated to them.
-        final int oldHeight = mShortcutsLayout.getChildAt(0).getLayoutParams().height;
-        final int newHeight = getResources().getDimensionPixelSize(R.dimen.bg_popup_item_height);
-        mHiddenShortcutsHeight = (newHeight - oldHeight) * mShortcutsLayout.getChildCount();
-
-        int numToHide = mShortcutsLayout.getChildCount() - maxShortcuts;
-        if (numToHide <= 0) {
-            return;
-        }
-        final int numShortcuts = mShortcutsLayout.getChildCount();
-        final int dir = hideFromTop ? 1 : -1;
-        for (int i = hideFromTop ? 0 : numShortcuts - 1; 0 <= i && i < numShortcuts; i += dir) {
-            View child = mShortcutsLayout.getChildAt(i);
-            if (child instanceof DeepShortcutView) {
-                mHiddenShortcutsHeight += child.getLayoutParams().height;
-                child.setVisibility(GONE);
-                int prev = i + dir;
-                if (!hideFromTop && 0 <= prev && prev < numShortcuts) {
-                    // When hiding views from the bottom, make sure to hide the last divider.
-                    mShortcutsLayout.getChildAt(prev).findViewById(R.id.divider).setVisibility(GONE);
-                }
-                numToHide--;
-                if (numToHide == 0) {
-                    break;
-                }
-            }
-        }
-    }
-
-    public int getHiddenShortcutsHeight() {
-        return mHiddenShortcutsHeight;
-    }
-
-    /**
-     * Sets all shortcuts in {@link #mShortcutsLayout} to VISIBLE, then creates an
-     * animation to reveal the newly shown shortcuts.
-     *
-     * @see #hideShortcuts(boolean, int)
-     */
-    public Animator showAllShortcuts(boolean showFromTop) {
-        // First set all the shortcuts to VISIBLE.
-        final int numShortcuts = mShortcutsLayout.getChildCount();
-        if (numShortcuts == 0) {
-            Log.w(TAG, "Tried to show all shortcuts but there were no shortcuts to show");
-            return null;
-        }
-        final int oldHeight = mShortcutsLayout.getChildAt(0).getLayoutParams().height;
-        final int newHeight = getResources().getDimensionPixelSize(R.dimen.bg_popup_item_height);
-        for (int i = 0; i < numShortcuts; i++) {
-            DeepShortcutView view = (DeepShortcutView) mShortcutsLayout.getChildAt(i);
-            view.getLayoutParams().height = newHeight;
-            view.requestLayout();
-            view.setVisibility(VISIBLE);
-            if (i < numShortcuts - 1) {
-                view.findViewById(R.id.divider).setVisibility(VISIBLE);
-            }
-        }
-
-        // Now reveal the newly shown shortcuts.
-        AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
-
-        if (showFromTop) {
-            // The new shortcuts pushed the original shortcuts down, but we want to animate them
-            // to that position. So we revert the translation and animate to the new.
-            animation.play(translateYFrom(mShortcutsLayout, -mHiddenShortcutsHeight));
-        } else if (mSystemShortcutIcons != null) {
-            // When adding the shortcuts from the bottom, things are a little trickier, since
-            // that means they push the icons header down. To account for this, we do the same
-            // translation trick as above, but on the header. Since this means leaving behind
-            // a blank area where the header was, we also need to clip the background.
-            animation.play(translateYFrom(mSystemShortcutIcons, -mHiddenShortcutsHeight));
-            // mPillRect is the bounds of this view before the new shortcuts were shown.
-            Rect backgroundStartRect = new Rect(mPillRect);
-            Rect backgroundEndRect = new Rect(mPillRect);
-            backgroundEndRect.bottom += mHiddenShortcutsHeight;
-            animation.play(new RoundedRectRevealOutlineProvider(getBackgroundRadius(),
-                    getBackgroundRadius(), backgroundStartRect, backgroundEndRect, mRoundedCorners)
-                    .createRevealAnimator(this, false));
-        }
-        for (int i = 0; i < numShortcuts; i++) {
-            // Animate each shortcut to its new height.
-            DeepShortcutView shortcut = (DeepShortcutView) mShortcutsLayout.getChildAt(i);
-            int heightDiff = newHeight - oldHeight;
-            int heightAdjustmentIndex = showFromTop ? numShortcuts - i - 1 : i;
-            int fromDir = showFromTop ? 1 : -1;
-            animation.play(translateYFrom(shortcut, heightDiff * heightAdjustmentIndex * fromDir));
-            // Make sure the text and icon stay centered in the shortcut.
-            animation.play(translateYFrom(shortcut.getBubbleText(), heightDiff / 2 * fromDir));
-            animation.play(translateYFrom(shortcut.getIconView(), heightDiff / 2 * fromDir));
-            // Scale icons back up to full size.
-            animation.play(LauncherAnimUtils.ofPropertyValuesHolder(shortcut.getIconView(),
-                    new PropertyListBuilder().scale(1f).build()));
-        }
-        return animation;
-    }
-
-    /**
-     * Animates the translationY of the view from the given offset to the view's current translation
-     * @return an Animator, which should be started by the caller.
-     */
-    private Animator translateYFrom(View v, int diff) {
-        float finalY = v.getTranslationY();
-        return ObjectAnimator.ofFloat(v, TRANSLATION_Y, finalY + diff, finalY);
-    }
-
-    /**
-     * Adds a {@link SystemShortcut.Widgets} item if there are widgets for the given ItemInfo.
-     */
-    public void enableWidgetsIfExist(final BubbleTextView originalIcon) {
-        ItemInfo itemInfo = (ItemInfo) originalIcon.getTag();
-        SystemShortcut widgetInfo = new SystemShortcut.Widgets();
-        View.OnClickListener onClickListener = widgetInfo.getOnClickListener(mLauncher, itemInfo);
-        View widgetsView = null;
-        for (View systemShortcutView : mSystemShortcutViews) {
-            if (systemShortcutView.getTag() instanceof SystemShortcut.Widgets) {
-                widgetsView = systemShortcutView;
-                break;
-            }
-        }
-        final PopupPopulator.Item widgetsItem = mSystemShortcutIcons == null
-                ? PopupPopulator.Item.SYSTEM_SHORTCUT
-                : PopupPopulator.Item.SYSTEM_SHORTCUT_ICON;
-        if (onClickListener != null && widgetsView == null) {
-            // We didn't have any widgets cached but now there are some, so enable the shortcut.
-            widgetsView = mLauncher.getLayoutInflater().inflate(widgetsItem.layoutId, this, false);
-            PopupPopulator.initializeSystemShortcut(getContext(), widgetsView, widgetInfo);
-            widgetsView.setOnClickListener(onClickListener);
-            if (widgetsItem == PopupPopulator.Item.SYSTEM_SHORTCUT_ICON) {
-                addShortcutView(widgetsView, widgetsItem, 0);
-            } else {
-                // If using the expanded system shortcut (as opposed to just the icon), we need to
-                // reopen the container to ensure measurements etc. all work out. While this could
-                // be quite janky, in practice the user would typically see a small flicker as the
-                // animation restarts partway through, and this is a very rare edge case anyway.
-                ((PopupContainerWithArrow) getParent()).close(false);
-                PopupContainerWithArrow.showForIcon(originalIcon);
-            }
-        } else if (onClickListener == null && widgetsView != null) {
-            // No widgets exist, but we previously added the shortcut so remove it.
-            if (widgetsItem == PopupPopulator.Item.SYSTEM_SHORTCUT_ICON) {
-                mSystemShortcutViews.remove(widgetsView);
-                mSystemShortcutIcons.removeView(widgetsView);
-            } else {
-                ((PopupContainerWithArrow) getParent()).close(false);
-                PopupContainerWithArrow.showForIcon(originalIcon);
-            }
-        }
-    }
-
-    @Override
-    public void fillInLogContainerData(View v, ItemInfo info, LauncherLogProto.Target target,
-            LauncherLogProto.Target targetParent) {
-        target.itemType = LauncherLogProto.ItemType.DEEPSHORTCUT;
-        target.rank = info.rank;
-        targetParent.containerType = LauncherLogProto.ContainerType.DEEPSHORTCUTS;
-    }
-}
diff --git a/src/com/android/launcher3/states/AllAppsState.java b/src/com/android/launcher3/states/AllAppsState.java
new file mode 100644
index 0000000..ed3023a
--- /dev/null
+++ b/src/com/android/launcher3/states/AllAppsState.java
@@ -0,0 +1,60 @@
+/*
+ * 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.states;
+
+import static com.android.launcher3.LauncherAnimUtils.ALL_APPS_TRANSITION_MS;
+
+import android.view.View;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.R;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+
+/**
+ * Definition for AllApps state
+ */
+public class AllAppsState extends LauncherState {
+
+    public static final String APPS_VIEW_SHOWN = "launcher.apps_view_shown";
+
+    private static final int STATE_FLAGS = FLAG_DISABLE_ACCESSIBILITY;
+
+    public AllAppsState(int id) {
+        super(id, ContainerType.ALLAPPS, ALL_APPS_TRANSITION_MS, 0f, STATE_FLAGS);
+    }
+
+    @Override
+    public void onStateEnabled(Launcher launcher) {
+        if (!launcher.getSharedPrefs().getBoolean(APPS_VIEW_SHOWN, false)) {
+            launcher.getSharedPrefs().edit().putBoolean(APPS_VIEW_SHOWN, true).apply();
+        }
+
+        AbstractFloatingView.closeAllOpenViews(launcher);
+        dispatchWindowStateChanged(launcher);
+    }
+
+    @Override
+    public String getDescription(Launcher launcher) {
+        return launcher.getString(R.string.all_apps_button_label);
+    }
+
+    @Override
+    public View getFinalFocus(Launcher launcher) {
+        return launcher.getAppsView();
+    }
+}
diff --git a/src/com/android/launcher3/states/InternalStateHandler.java b/src/com/android/launcher3/states/InternalStateHandler.java
new file mode 100644
index 0000000..f084fd2
--- /dev/null
+++ b/src/com/android/launcher3/states/InternalStateHandler.java
@@ -0,0 +1,68 @@
+/*
+ * 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.states;
+
+import android.content.Intent;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.Launcher.OnResumeCallback;
+
+/**
+ * Utility class to sending state handling logic to Launcher from within the same process.
+ *
+ * Extending {@link Binder} ensures that the platform maintains a single instance of each object
+ * which allows this object to safely navigate the system process.
+ */
+public abstract class InternalStateHandler extends Binder implements OnResumeCallback {
+
+    public static final String EXTRA_STATE_HANDLER = "launcher.state_handler";
+
+    protected abstract void init(Launcher launcher, boolean alreadyOnHome);
+
+    public final Intent addToIntent(Intent intent) {
+        Bundle extras = new Bundle();
+        extras.putBinder(EXTRA_STATE_HANDLER, this);
+        intent.putExtras(extras);
+        return intent;
+    }
+
+    public static boolean handleCreate(Launcher launcher, Intent intent) {
+        return handleIntent(launcher, intent, false);
+    }
+
+    public static boolean handleNewIntent(Launcher launcher, Intent intent, boolean alreadyOnHome) {
+        return handleIntent(launcher, intent, alreadyOnHome);
+    }
+
+    private static boolean handleIntent(
+            Launcher launcher, Intent intent, boolean alreadyOnHome) {
+        boolean result = false;
+        if (intent != null && intent.getExtras() != null) {
+            IBinder stateBinder = intent.getExtras().getBinder(EXTRA_STATE_HANDLER);
+            if (stateBinder instanceof InternalStateHandler) {
+                InternalStateHandler handler = (InternalStateHandler) stateBinder;
+                launcher.setOnResumeCallback(handler);
+                handler.init(launcher, alreadyOnHome);
+                result = true;
+            }
+            intent.getExtras().remove(EXTRA_STATE_HANDLER);
+        }
+        return result;
+    }
+}
diff --git a/src/com/android/launcher3/states/SpringLoadedState.java b/src/com/android/launcher3/states/SpringLoadedState.java
new file mode 100644
index 0000000..3864e3a
--- /dev/null
+++ b/src/com/android/launcher3/states/SpringLoadedState.java
@@ -0,0 +1,114 @@
+/*
+ * 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.states;
+
+import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_TRANSITION_MS;
+
+import android.content.pm.ActivityInfo;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.view.View;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.InstallShortcutReceiver;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.Workspace;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+
+/**
+ * Definition for spring loaded state used during drag and drop.
+ */
+public class SpringLoadedState extends LauncherState {
+
+    private static final int STATE_FLAGS = FLAG_SHOW_SCRIM | FLAG_MULTI_PAGE |
+            FLAG_DISABLE_ACCESSIBILITY | FLAG_DO_NOT_RESTORE;
+
+    // Determines how long to wait after a rotation before restoring the screen orientation to
+    // match the sensor state.
+    private static final int RESTORE_SCREEN_ORIENTATION_DELAY = 500;
+
+    public SpringLoadedState(int id) {
+        super(id, ContainerType.OVERVIEW, SPRING_LOADED_TRANSITION_MS, 1f, STATE_FLAGS);
+    }
+
+    @Override
+    public float[] getWorkspaceScaleAndTranslation(Launcher launcher) {
+        DeviceProfile grid = launcher.getDeviceProfile();
+        Workspace ws = launcher.getWorkspace();
+        if (grid.isVerticalBarLayout() || ws.getChildCount() == 0) {
+            return super.getWorkspaceScaleAndTranslation(launcher);
+        }
+
+        float scale = grid.workspaceSpringLoadShrinkFactor;
+        Rect insets = launcher.getDragLayer().getInsets();
+
+        float scaledHeight = scale * ws.getNormalChildHeight();
+        float shrunkTop = insets.top + grid.dropTargetBarSizePx;
+        float shrunkBottom = ws.getViewportHeight() - insets.bottom
+                - grid.getWorkspacePadding(null).bottom
+                - grid.workspaceSpringLoadedBottomSpace;
+        float totalShrunkSpace = shrunkBottom - shrunkTop;
+
+        float desiredCellTop = shrunkTop + (totalShrunkSpace - scaledHeight) / 2;
+
+        float halfHeight = ws.getHeight() / 2;
+        float myCenter = ws.getTop() + halfHeight;
+        float cellTopFromCenter = halfHeight - ws.getChildAt(0).getTop();
+        float actualCellTop = myCenter - cellTopFromCenter * scale;
+        return new float[] { scale, (desiredCellTop - actualCellTop) / scale};
+    }
+
+    @Override
+    public void onStateEnabled(Launcher launcher) {
+        Workspace ws = launcher.getWorkspace();
+        ws.showPageIndicatorAtCurrentScroll();
+        ws.getPageIndicator().setShouldAutoHide(false);
+
+        // Lock the orientation:
+        if (launcher.isRotationEnabled()) {
+            launcher.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
+        }
+
+        // Prevent any Un/InstallShortcutReceivers from updating the db while we are
+        // in spring loaded mode
+        InstallShortcutReceiver.enableInstallQueue(InstallShortcutReceiver.FLAG_DRAG_AND_DROP);
+    }
+
+    @Override
+    public void onStateDisabled(final Launcher launcher) {
+        launcher.getWorkspace().getPageIndicator().setShouldAutoHide(true);
+
+        // Unlock rotation lock
+        if (launcher.isRotationEnabled()) {
+            new Handler().postDelayed(new Runnable() {
+                @Override
+                public void run() {
+                    launcher.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
+                }
+            }, RESTORE_SCREEN_ORIENTATION_DELAY);
+        }
+
+        // Re-enable any Un/InstallShortcutReceiver and now process any queued items
+        InstallShortcutReceiver.disableAndFlushInstallQueue(
+                InstallShortcutReceiver.FLAG_DRAG_AND_DROP, launcher);
+    }
+
+    @Override
+    public View getFinalFocus(Launcher launcher) {
+        return null;
+    }
+}
diff --git a/src/com/android/launcher3/testing/DummyWidget.java b/src/com/android/launcher3/testing/DummyWidget.java
deleted file mode 100644
index df887ac..0000000
--- a/src/com/android/launcher3/testing/DummyWidget.java
+++ /dev/null
@@ -1,53 +0,0 @@
-package com.android.launcher3.testing;
-
-import android.appwidget.AppWidgetProviderInfo;
-
-import com.android.launcher3.CustomAppWidget;
-import com.android.launcher3.R;
-
-public class DummyWidget implements CustomAppWidget {
-    @Override
-    public String getLabel() {
-        return "Dumb Launcher Widget";
-    }
-
-    @Override
-    public int getPreviewImage() {
-        return 0;
-    }
-
-    @Override
-    public int getIcon() {
-        return 0;
-    }
-
-    @Override
-    public int getWidgetLayout() {
-        return R.layout.zzz_dummy_widget;
-    }
-
-    @Override
-    public int getSpanX() {
-        return 2;
-    }
-
-    @Override
-    public int getSpanY() {
-        return 2;
-    }
-
-    @Override
-    public int getMinSpanX() {
-        return 1;
-    }
-
-    @Override
-    public int getMinSpanY() {
-        return 1;
-    }
-
-    @Override
-    public int getResizeMode() {
-        return AppWidgetProviderInfo.RESIZE_BOTH;
-    }
-}
diff --git a/src/com/android/launcher3/testing/LauncherExtension.java b/src/com/android/launcher3/testing/LauncherExtension.java
deleted file mode 100644
index 5cb8c4c..0000000
--- a/src/com/android/launcher3/testing/LauncherExtension.java
+++ /dev/null
@@ -1,219 +0,0 @@
-package com.android.launcher3.testing;
-
-import android.content.Intent;
-import android.graphics.Color;
-import android.os.Bundle;
-import android.view.Menu;
-import android.view.View;
-import android.widget.FrameLayout;
-
-import com.android.launcher3.AppInfo;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherCallbacks;
-import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.ComponentKeyMapper;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * This class represents a very trivial LauncherExtension. It primarily serves as a simple
- * class to exercise the LauncherOverlay interface.
- */
-public class LauncherExtension extends Launcher {
-
-    //------ Activity methods -------//
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        setLauncherCallbacks(new LauncherExtensionCallbacks());
-        super.onCreate(savedInstanceState);
-    }
-
-    public class LauncherExtensionCallbacks implements LauncherCallbacks {
-
-        @Override
-        public void preOnCreate() {
-        }
-
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-        }
-
-        @Override
-        public void preOnResume() {
-        }
-
-        @Override
-        public void onResume() {
-        }
-
-        @Override
-        public void onStart() {
-        }
-
-        @Override
-        public void onStop() {
-        }
-
-        @Override
-        public void onPause() {
-        }
-
-        @Override
-        public void onDestroy() {
-        }
-
-        @Override
-        public void onSaveInstanceState(Bundle outState) {
-        }
-
-        @Override
-        public void onPostCreate(Bundle savedInstanceState) {
-        }
-
-        @Override
-        public void onNewIntent(Intent intent) {
-        }
-
-        @Override
-        public void onActivityResult(int requestCode, int resultCode, Intent data) {
-        }
-
-        @Override
-        public void onRequestPermissionsResult(int requestCode, String[] permissions,
-                int[] grantResults) {
-        }
-
-        @Override
-        public void onWindowFocusChanged(boolean hasFocus) {
-        }
-
-        @Override
-        public boolean onPrepareOptionsMenu(Menu menu) {
-            return false;
-        }
-
-        @Override
-        public void dump(String prefix, FileDescriptor fd, PrintWriter w, String[] args) {
-        }
-
-        @Override
-        public void onHomeIntent() {
-        }
-
-        @Override
-        public boolean handleBackPressed() {
-            return false;
-        }
-
-        @Override
-        public void onTrimMemory(int level) {
-        }
-
-        @Override
-        public void onLauncherProviderChange() {
-        }
-
-        @Override
-        public void finishBindingItems(boolean upgradePath) {
-        }
-
-        @Override
-        public void bindAllApplications(ArrayList<AppInfo> apps) {
-        }
-
-        @Override
-        public void onWorkspaceLockedChanged() {
-        }
-
-        @Override
-        public void onInteractionBegin() {
-        }
-
-        @Override
-        public void onInteractionEnd() {
-        }
-
-        @Override
-        public boolean startSearch(String initialQuery, boolean selectInitialQuery,
-                Bundle appSearchData) {
-            return false;
-        }
-
-        CustomContentCallbacks mCustomContentCallbacks = new CustomContentCallbacks() {
-
-            // Custom content is completely shown. {@code fromResume} indicates whether this was caused
-            // by a onResume or by scrolling otherwise.
-            public void onShow(boolean fromResume) {
-            }
-
-            // Custom content is completely hidden
-            public void onHide() {
-            }
-
-            // Custom content scroll progress changed. From 0 (not showing) to 1 (fully showing).
-            public void onScrollProgressChanged(float progress) {
-
-            }
-
-            // Indicates whether the user is allowed to scroll away from the custom content.
-            public boolean isScrollingAllowed() {
-                return true;
-            }
-
-        };
-
-        @Override
-        public boolean hasCustomContentToLeft() {
-            return true;
-        }
-
-        @Override
-        public void populateCustomContentContainer() {
-            FrameLayout customContent = new FrameLayout(LauncherExtension.this);
-            customContent.setBackgroundColor(Color.GRAY);
-            addToCustomContentPage(customContent, mCustomContentCallbacks, "");
-        }
-
-        @Override
-        public View getQsbBar() {
-            return null;
-        }
-
-        @Override
-        public Bundle getAdditionalSearchWidgetOptions() {
-            return new Bundle();
-        }
-
-        @Override
-        public boolean shouldMoveToDefaultScreenOnHomeIntent() {
-            return true;
-        }
-
-        @Override
-        public boolean hasSettings() {
-            return false;
-        }
-
-        @Override
-        public List<ComponentKeyMapper<AppInfo>> getPredictedApps() {
-            // To debug app predictions, enable AlphabeticalAppsList#DEBUG_PREDICTIONS
-            return new ArrayList<>();
-        }
-
-        @Override
-        public int getSearchBarHeight() {
-            return SEARCH_BAR_HEIGHT_NORMAL;
-        }
-
-        @Override
-        public void onAttachedToWindow() {
-        }
-
-        @Override
-        public void onDetachedFromWindow() {
-        }
-    }
-}
diff --git a/src/com/android/launcher3/testing/MemoryDumpActivity.java b/src/com/android/launcher3/testing/MemoryDumpActivity.java
deleted file mode 100644
index 9bcf92b..0000000
--- a/src/com/android/launcher3/testing/MemoryDumpActivity.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Copyright (C) 2013 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.testing;
-
-import android.app.Activity;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.content.pm.PackageManager;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Environment;
-import android.os.IBinder;
-import android.util.Log;
-
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipOutputStream;
-
-public class MemoryDumpActivity extends Activity {
-    private static final String TAG = "MemoryDumpActivity";
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-    }
-
-    public static String zipUp(ArrayList<String> paths) {
-        final int BUFSIZ = 256 * 1024; // 256K
-        final byte[] buf = new byte[BUFSIZ];
-        final String zipfilePath = String.format("%s/hprof-%d.zip",
-                Environment.getExternalStorageDirectory(),
-                System.currentTimeMillis());
-        ZipOutputStream zos = null;
-        try {
-            OutputStream os = new FileOutputStream(zipfilePath);
-            zos = new ZipOutputStream(new BufferedOutputStream(os));
-            for (String filename : paths) {
-                InputStream is = null;
-                try {
-                    is = new BufferedInputStream(new FileInputStream(filename));
-                    ZipEntry entry = new ZipEntry(filename);
-                    zos.putNextEntry(entry);
-                    int len;
-                    while ( 0 < (len = is.read(buf, 0, BUFSIZ)) ) {
-                        zos.write(buf, 0, len);
-                    }
-                    zos.closeEntry();
-                } finally {
-                    is.close();
-                }
-            }
-        } catch (IOException e) {
-            Log.e(TAG, "error zipping up profile data", e);
-            return null;
-        } finally {
-            if (zos != null) {
-                try {
-                    zos.close();
-                } catch (IOException e) {
-                    // ugh, whatever
-                }
-            }
-        }
-        return zipfilePath;
-    }
-
-    public static void dumpHprofAndShare(final Context context, MemoryTracker tracker) {
-        final StringBuilder body = new StringBuilder();
-
-        final ArrayList<String> paths = new ArrayList<String>();
-        final int myPid = android.os.Process.myPid();
-
-        final int[] pids_orig = tracker.getTrackedProcesses();
-        final int[] pids_copy = Arrays.copyOf(pids_orig, pids_orig.length);
-        for (int pid : pids_copy) {
-            MemoryTracker.ProcessMemInfo info = tracker.getMemInfo(pid);
-            if (info != null) {
-                body.append("pid ").append(pid).append(":")
-                    .append(" up=").append(info.getUptime())
-                    .append(" pss=").append(info.currentPss)
-                    .append(" uss=").append(info.currentUss)
-                    .append("\n");
-            }
-            if (pid == myPid) {
-                final String path = String.format("%s/launcher-memory-%d.ahprof",
-                        Environment.getExternalStorageDirectory(),
-                        pid);
-                Log.v(TAG, "Dumping memory info for process " + pid + " to " + path);
-                try {
-                    android.os.Debug.dumpHprofData(path); // will block
-                } catch (IOException e) {
-                    Log.e(TAG, "error dumping memory:", e);
-                }
-                paths.add(path);
-            }
-        }
-
-        String zipfile = zipUp(paths);
-
-        if (zipfile == null) return;
-
-        Intent shareIntent = new Intent(Intent.ACTION_SEND);
-        shareIntent.setType("application/zip");
-
-        final PackageManager pm = context.getPackageManager();
-        shareIntent.putExtra(Intent.EXTRA_SUBJECT, String.format("Launcher memory dump (%d)", myPid));
-        String appVersion;
-        try {
-            appVersion = pm.getPackageInfo(context.getPackageName(), 0).versionName;
-        } catch (PackageManager.NameNotFoundException e) {
-            appVersion = "?";
-        }
-
-        body.append("\nApp version: ").append(appVersion).append("\nBuild: ").append(Build.DISPLAY).append("\n");
-        shareIntent.putExtra(Intent.EXTRA_TEXT, body.toString());
-
-        final File pathFile = new File(zipfile);
-        final Uri pathUri = Uri.fromFile(pathFile);
-
-        shareIntent.putExtra(Intent.EXTRA_STREAM, pathUri);
-        context.startActivity(shareIntent);
-    }
-
-    @Override
-    public void onStart() {
-        super.onStart();
-
-        startDump(this, new Runnable() {
-            @Override
-            public void run() {
-                finish();
-            }
-        });
-    }
-
-    public static void startDump(final Context context) {
-        startDump(context, null);
-    }
-
-    public static void startDump(final Context context, final Runnable andThen) {
-        final ServiceConnection connection = new ServiceConnection() {
-            public void onServiceConnected(ComponentName className, IBinder service) {
-                Log.v(TAG, "service connected, dumping...");
-                dumpHprofAndShare(context,
-                        ((MemoryTracker.MemoryTrackerInterface) service).getService());
-                context.unbindService(this);
-                if (andThen != null) andThen.run();
-            }
-
-            public void onServiceDisconnected(ComponentName className) {
-            }
-        };
-        Log.v(TAG, "attempting to bind to memory tracker");
-        context.bindService(new Intent(context, MemoryTracker.class),
-                connection, Context.BIND_AUTO_CREATE);
-    }
-}
diff --git a/src/com/android/launcher3/testing/MemoryTracker.java b/src/com/android/launcher3/testing/MemoryTracker.java
deleted file mode 100644
index ed2a312..0000000
--- a/src/com/android/launcher3/testing/MemoryTracker.java
+++ /dev/null
@@ -1,217 +0,0 @@
-/*
- * Copyright (C) 2013 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.testing;
-
-import android.app.ActivityManager;
-import android.app.Service;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Binder;
-import android.os.Debug;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Message;
-import android.os.SystemClock;
-import android.util.Log;
-import android.util.LongSparseArray;
-
-import com.android.launcher3.util.TestingUtils;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class MemoryTracker extends Service {
-    public static final String TAG = MemoryTracker.class.getSimpleName();
-
-    private static final long UPDATE_RATE = 5000;
-
-    private static final int MSG_START = 1;
-    private static final int MSG_STOP = 2;
-    private static final int MSG_UPDATE = 3;
-
-    public static class ProcessMemInfo {
-        public int pid;
-        public String name;
-        public long startTime;
-        public long currentPss, currentUss;
-        public long[] pss = new long[256];
-        public long[] uss = new long[256];
-            //= new Meminfo[(int) (30 * 60 / (UPDATE_RATE / 1000))]; // 30 minutes
-        public long max = 1;
-        public int head = 0;
-        public ProcessMemInfo(int pid, String name, long start) {
-            this.pid = pid;
-            this.name = name;
-            this.startTime = start;
-        }
-        public long getUptime() {
-            return System.currentTimeMillis() - startTime;
-        }
-    };
-    public final LongSparseArray<ProcessMemInfo> mData = new LongSparseArray<ProcessMemInfo>();
-    public final ArrayList<Long> mPids = new ArrayList<Long>();
-    private int[] mPidsArray = new int[0];
-    private final Object mLock = new Object();
-
-    Handler mHandler = new Handler() {
-        @Override
-        public void handleMessage(Message m) {
-            switch (m.what) {
-                case MSG_START:
-                    mHandler.removeMessages(MSG_UPDATE);
-                    mHandler.sendEmptyMessage(MSG_UPDATE);
-                    break;
-                case MSG_STOP:
-                    mHandler.removeMessages(MSG_UPDATE);
-                    break;
-                case MSG_UPDATE:
-                    update();
-                    mHandler.removeMessages(MSG_UPDATE);
-                    mHandler.sendEmptyMessageDelayed(MSG_UPDATE, UPDATE_RATE);
-                    break;
-            }
-        }
-    };
-
-    ActivityManager mAm;
-
-    public ProcessMemInfo getMemInfo(int pid) {
-        return mData.get(pid);
-    }
-
-    public int[] getTrackedProcesses() {
-        return mPidsArray;
-    }
-
-    public void startTrackingProcess(int pid, String name, long start) {
-        synchronized (mLock) {
-            final Long lpid = Long.valueOf(pid);
-
-            if (mPids.contains(lpid)) return;
-
-            mPids.add(lpid);
-            updatePidsArrayL();
-
-            mData.put(pid, new ProcessMemInfo(pid, name, start));
-        }
-    }
-
-    void updatePidsArrayL() {
-        final int N = mPids.size();
-        mPidsArray = new int[N];
-        StringBuffer sb = new StringBuffer("Now tracking processes: ");
-        for (int i=0; i<N; i++) {
-            final int p = mPids.get(i).intValue();
-            mPidsArray[i] = p;
-            sb.append(p); sb.append(" ");
-        }
-        Log.v(TAG, sb.toString());
-    }
-
-    void update() {
-        synchronized (mLock) {
-            Debug.MemoryInfo[] dinfos = mAm.getProcessMemoryInfo(mPidsArray);
-            for (int i=0; i<dinfos.length; i++) {
-                Debug.MemoryInfo dinfo = dinfos[i];
-                if (i > mPids.size()) {
-                    Log.e(TAG, "update: unknown process info received: " + dinfo);
-                    break;
-                }
-                final long pid = mPids.get(i).intValue();
-                final ProcessMemInfo info = mData.get(pid);
-                info.head = (info.head+1) % info.pss.length;
-                info.pss[info.head] = info.currentPss = dinfo.getTotalPss();
-                info.uss[info.head] = info.currentUss = dinfo.getTotalPrivateDirty();
-                if (info.currentPss > info.max) info.max = info.currentPss;
-                if (info.currentUss > info.max) info.max = info.currentUss;
-                // Log.v(TAG, "update: pid " + pid + " pss=" + info.currentPss + " uss=" + info.currentUss);
-                if (info.currentPss == 0) {
-                    Log.v(TAG, "update: pid " + pid + " has pss=0, it probably died");
-                    mData.remove(pid);
-                }
-            }
-            for (int i=mPids.size()-1; i>=0; i--) {
-                final long pid = mPids.get(i).intValue();
-                if (mData.get(pid) == null) {
-                    mPids.remove(i);
-                    updatePidsArrayL();
-                }
-            }
-        }
-    }
-
-    @Override
-    public void onCreate() {
-        mAm = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
-
-        // catch up in case we crashed but other processes are still running
-        List<ActivityManager.RunningServiceInfo> svcs = mAm.getRunningServices(256);
-        for (ActivityManager.RunningServiceInfo svc : svcs) {
-            if (svc.service.getPackageName().equals(getPackageName())) {
-                Log.v(TAG, "discovered running service: " + svc.process + " (" + svc.pid + ")");
-                startTrackingProcess(svc.pid, svc.process,
-                        System.currentTimeMillis() - (SystemClock.elapsedRealtime() - svc.activeSince));
-            }
-        }
-
-        List<ActivityManager.RunningAppProcessInfo> procs = mAm.getRunningAppProcesses();
-        for (ActivityManager.RunningAppProcessInfo proc : procs) {
-            final String pname = proc.processName;
-            if (pname.startsWith(getPackageName())) {
-                Log.v(TAG, "discovered other running process: " + pname + " (" + proc.pid + ")");
-                startTrackingProcess(proc.pid, pname, System.currentTimeMillis());
-            }
-        }
-    }
-
-    @Override
-    public void onDestroy() {
-        mHandler.sendEmptyMessage(MSG_STOP);
-    }
-
-    @Override
-    public int onStartCommand(Intent intent, int flags, int startId) {
-        Log.v(TAG, "Received start id " + startId + ": " + intent);
-
-        if (intent != null) {
-            if (TestingUtils.ACTION_START_TRACKING.equals(intent.getAction())) {
-                final int pid = intent.getIntExtra("pid", -1);
-                final String name = intent.getStringExtra("name");
-                final long start = intent.getLongExtra("start", System.currentTimeMillis());
-                startTrackingProcess(pid, name, start);
-            }
-        }
-
-        mHandler.sendEmptyMessage(MSG_START);
-
-        return START_STICKY;
-    }
-
-    public class MemoryTrackerInterface extends Binder {
-        MemoryTracker getService() {
-            return MemoryTracker.this;
-        }
-    }
-
-    private final IBinder mBinder = new MemoryTrackerInterface();
-
-    public IBinder onBind(Intent intent) {
-        mHandler.sendEmptyMessage(MSG_START);
-
-        return mBinder;
-    }
-}
diff --git a/src/com/android/launcher3/testing/ToggleWeightWatcher.java b/src/com/android/launcher3/testing/ToggleWeightWatcher.java
deleted file mode 100644
index f0c3920..0000000
--- a/src/com/android/launcher3/testing/ToggleWeightWatcher.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package com.android.launcher3.testing;
-
-import android.app.Activity;
-import android.content.SharedPreferences;
-import android.os.Bundle;
-import android.view.View;
-
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.util.TestingUtils;
-
-public class ToggleWeightWatcher extends Activity {
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        SharedPreferences sp = Utilities.getPrefs(this);
-        boolean show = sp.getBoolean(TestingUtils.SHOW_WEIGHT_WATCHER, true);
-
-        show = !show;
-        sp.edit().putBoolean(TestingUtils.SHOW_WEIGHT_WATCHER, show).apply();
-
-        Launcher launcher = (Launcher) LauncherAppState.getInstance(this).getModel().getCallback();
-        if (launcher != null && launcher.mWeightWatcher != null) {
-            launcher.mWeightWatcher.setVisibility(show ? View.VISIBLE : View.GONE);
-        }
-        finish();
-    }
-}
diff --git a/src/com/android/launcher3/testing/WeightWatcher.java b/src/com/android/launcher3/testing/WeightWatcher.java
deleted file mode 100644
index a26a2b6..0000000
--- a/src/com/android/launcher3/testing/WeightWatcher.java
+++ /dev/null
@@ -1,267 +0,0 @@
-/*
- * Copyright (C) 2013 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.testing;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Message;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.TypedValue;
-import android.view.Gravity;
-import android.view.View;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.android.launcher3.util.Thunk;
-
-public class WeightWatcher extends LinearLayout {
-    private static final int RAM_GRAPH_RSS_COLOR = 0xFF990000;
-    private static final int RAM_GRAPH_PSS_COLOR = 0xFF99CC00;
-    private static final int TEXT_COLOR = 0xFFFFFFFF;
-    private static final int BACKGROUND_COLOR = 0xc0000000;
-
-    private static final int UPDATE_RATE = 5000;
-
-    private static final int MSG_START = 1;
-    private static final int MSG_STOP = 2;
-    private static final int MSG_UPDATE = 3;
-
-    static int indexOf(int[] a, int x) {
-        for (int i=0; i<a.length; i++) {
-            if (a[i] == x) return i;
-        }
-        return -1;
-    }
-
-    Handler mHandler = new Handler() {
-        @Override
-        public void handleMessage(Message m) {
-            switch (m.what) {
-                case MSG_START:
-                    mHandler.sendEmptyMessage(MSG_UPDATE);
-                    break;
-                case MSG_STOP:
-                    mHandler.removeMessages(MSG_UPDATE);
-                    break;
-                case MSG_UPDATE:
-                    int[] pids = mMemoryService.getTrackedProcesses();
-
-                    final int N = getChildCount();
-                    if (pids.length != N) initViews();
-                    else for (int i=0; i<N; i++) {
-                        ProcessWatcher pw = ((ProcessWatcher) getChildAt(i));
-                        if (indexOf(pids, pw.getPid()) < 0) {
-                            initViews();
-                            break;
-                        }
-                        pw.update();
-                    }
-                    mHandler.sendEmptyMessageDelayed(MSG_UPDATE, UPDATE_RATE);
-                    break;
-            }
-        }
-    };
-    @Thunk MemoryTracker mMemoryService;
-
-    public WeightWatcher(Context context, AttributeSet attrs) {
-        super(context, attrs);
-
-        ServiceConnection connection = new ServiceConnection() {
-            public void onServiceConnected(ComponentName className, IBinder service) {
-                mMemoryService = ((MemoryTracker.MemoryTrackerInterface)service).getService();
-                initViews();
-            }
-
-            public void onServiceDisconnected(ComponentName className) {
-                mMemoryService = null;
-            }
-        };
-        context.bindService(new Intent(context, MemoryTracker.class),
-                connection, Context.BIND_AUTO_CREATE);
-
-        setOrientation(LinearLayout.VERTICAL);
-
-        setBackgroundColor(BACKGROUND_COLOR);
-    }
-
-    public void initViews() {
-        removeAllViews();
-        int[] processes = mMemoryService.getTrackedProcesses();
-        for (int i=0; i<processes.length; i++) {
-            final ProcessWatcher v = new ProcessWatcher(getContext());
-            v.setPid(processes[i]);
-            addView(v);
-        }
-    }
-
-    @Override
-    public void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        mHandler.sendEmptyMessage(MSG_START);
-    }
-
-    @Override
-    public void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        mHandler.sendEmptyMessage(MSG_STOP);
-    }
-
-    public class ProcessWatcher extends LinearLayout {
-        GraphView mRamGraph;
-        TextView mText;
-        int mPid;
-        @Thunk MemoryTracker.ProcessMemInfo mMemInfo;
-
-        public ProcessWatcher(Context context) {
-            this(context, null);
-        }
-
-        public ProcessWatcher(Context context, AttributeSet attrs) {
-            super(context, attrs);
-
-            final float dp = getResources().getDisplayMetrics().density;
-
-            mText = new TextView(getContext());
-            mText.setTextColor(TEXT_COLOR);
-            mText.setTextSize(TypedValue.COMPLEX_UNIT_PX, 10 * dp);
-            mText.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL);
-
-            final int p = (int)(2*dp);
-            setPadding(p, 0, p, 0);
-
-            mRamGraph = new GraphView(getContext());
-
-            LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
-                    0,
-                    (int)(14 * dp),
-                    1f
-            );
-
-            addView(mText, params);
-            params.leftMargin = (int)(4*dp);
-            params.weight = 0f;
-            params.width = (int)(200 * dp);
-            addView(mRamGraph, params);
-        }
-
-        public void setPid(int pid) {
-            mPid = pid;
-            mMemInfo = mMemoryService.getMemInfo(mPid);
-            if (mMemInfo == null) {
-                Log.v("WeightWatcher", "Missing info for pid " + mPid + ", removing view: " + this);
-                initViews();
-            }
-        }
-
-        public int getPid() {
-            return mPid;
-        }
-
-        public String getUptimeString() {
-            long sec = mMemInfo.getUptime() / 1000;
-            StringBuilder sb = new StringBuilder();
-            long days = sec / 86400;
-            if (days > 0) {
-                sec -= days * 86400;
-                sb.append(days);
-                sb.append("d");
-            }
-
-            long hours = sec / 3600;
-            if (hours > 0) {
-                sec -= hours * 3600;
-                sb.append(hours);
-                sb.append("h");
-            }
-
-            long mins = sec / 60;
-            if (mins > 0) {
-                sec -= mins * 60;
-                sb.append(mins);
-                sb.append("m");
-            }
-
-            sb.append(sec);
-            sb.append("s");
-            return sb.toString();
-        }
-
-        public void update() {
-            //Log.v("WeightWatcher.ProcessWatcher",
-            //        "MSG_UPDATE pss=" + mMemInfo.currentPss);
-            mText.setText("(" + mPid
-                          + (mPid == android.os.Process.myPid()
-                                ? "/A"  // app
-                                : "/S") // service
-                          + ") up " + getUptimeString()
-                          + " P=" + mMemInfo.currentPss
-                          + " U=" + mMemInfo.currentUss
-                          );
-            mRamGraph.invalidate();
-        }
-
-        public class GraphView extends View {
-            Paint pssPaint, ussPaint, headPaint;
-
-            public GraphView(Context context, AttributeSet attrs) {
-                super(context, attrs);
-
-                pssPaint = new Paint();
-                pssPaint.setColor(RAM_GRAPH_PSS_COLOR);
-                ussPaint = new Paint();
-                ussPaint.setColor(RAM_GRAPH_RSS_COLOR);
-                headPaint = new Paint();
-                headPaint.setColor(Color.WHITE);
-            }
-
-            public GraphView(Context context) {
-                this(context, null);
-            }
-
-            @Override
-            public void onDraw(Canvas c) {
-                int w = c.getWidth();
-                int h = c.getHeight();
-
-                if (mMemInfo == null) return;
-
-                final int N = mMemInfo.pss.length;
-                final float barStep = (float) w / N;
-                final float barWidth = Math.max(1, barStep);
-                final float scale = (float) h / mMemInfo.max;
-
-                int i;
-                float x;
-                for (i=0; i<N; i++) {
-                    x = i * barStep;
-                    c.drawRect(x, h - scale * mMemInfo.pss[i], x + barWidth, h, pssPaint);
-                    c.drawRect(x, h - scale * mMemInfo.uss[i], x + barWidth, h, ussPaint);
-                }
-                x = mMemInfo.head * barStep;
-                c.drawRect(x, 0, x + barWidth, h, headPaint);
-            }
-        }
-    }
-}
diff --git a/src/com/android/launcher3/touch/SwipeDetector.java b/src/com/android/launcher3/touch/SwipeDetector.java
index be4648e..351f88d 100644
--- a/src/com/android/launcher3/touch/SwipeDetector.java
+++ b/src/com/android/launcher3/touch/SwipeDetector.java
@@ -16,6 +16,7 @@
 package com.android.launcher3.touch;
 
 import static android.view.MotionEvent.INVALID_POINTER_ID;
+
 import android.content.Context;
 import android.graphics.PointF;
 import android.support.annotation.NonNull;
@@ -23,7 +24,6 @@
 import android.util.Log;
 import android.view.MotionEvent;
 import android.view.ViewConfiguration;
-import android.view.animation.Interpolator;
 
 /**
  * One dimensional scroll/drag/swipe gesture detector.
@@ -43,7 +43,6 @@
     public static final int DIRECTION_BOTH = DIRECTION_NEGATIVE | DIRECTION_POSITIVE;
 
     private static final float ANIMATION_DURATION = 1200;
-    private static final float FAST_FLING_PX_MS = 10;
 
     protected int mActivePointerId = INVALID_POINTER_ID;
 
@@ -351,22 +350,4 @@
         }
         return duration;
     }
-
-    public static class ScrollInterpolator implements Interpolator {
-
-        boolean mSteeper;
-
-        public void setVelocityAtZero(float velocity) {
-            mSteeper = velocity > FAST_FLING_PX_MS;
-        }
-
-        public float getInterpolation(float t) {
-            t -= 1.0f;
-            float output = t * t * t;
-            if (mSteeper) {
-                output *= t * t; // Make interpolation initial slope steeper
-            }
-            return output + 1;
-        }
-    }
 }
diff --git a/src/com/android/launcher3/util/FlingAnimation.java b/src/com/android/launcher3/util/FlingAnimation.java
index d475ee9..fe0571b 100644
--- a/src/com/android/launcher3/util/FlingAnimation.java
+++ b/src/com/android/launcher3/util/FlingAnimation.java
@@ -1,5 +1,7 @@
 package com.android.launcher3.util;
 
+import static com.android.launcher3.LauncherState.NORMAL;
+
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
@@ -95,7 +97,7 @@
         Runnable onAnimationEndRunnable = new Runnable() {
             @Override
             public void run() {
-                mLauncher.exitSpringLoadedDragMode();
+                mLauncher.getStateManager().goToState(NORMAL);
                 mDropTarget.completeDrop(mDragObject);
             }
         };
diff --git a/src/com/android/launcher3/util/InstantAppResolver.java b/src/com/android/launcher3/util/InstantAppResolver.java
index e60d768..99ce7ca 100644
--- a/src/com/android/launcher3/util/InstantAppResolver.java
+++ b/src/com/android/launcher3/util/InstantAppResolver.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 
+import com.android.launcher3.AppInfo;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 
@@ -39,6 +40,10 @@
         return false;
     }
 
+    public boolean isInstantApp(AppInfo info) {
+        return false;
+    }
+
     public List<ApplicationInfo> getInstantApps() {
         return Collections.emptyList();
     }
diff --git a/src/com/android/launcher3/util/ItemInfoMatcher.java b/src/com/android/launcher3/util/ItemInfoMatcher.java
index 18787b6..daedaef 100644
--- a/src/com/android/launcher3/util/ItemInfoMatcher.java
+++ b/src/com/android/launcher3/util/ItemInfoMatcher.java
@@ -93,6 +93,19 @@
         };
     }
 
+    /**
+     * Returns a new matcher which returns the opposite boolean value of the provided
+     * {@param matcher}.
+     */
+    public static ItemInfoMatcher not(final ItemInfoMatcher matcher) {
+        return new ItemInfoMatcher() {
+            @Override
+            public boolean matches(ItemInfo info, ComponentName cn) {
+                return !matcher.matches(info, cn);
+            }
+        };
+    }
+
     public static ItemInfoMatcher ofUser(final UserHandle user) {
         return new ItemInfoMatcher() {
             @Override
diff --git a/src/com/android/launcher3/util/TestingUtils.java b/src/com/android/launcher3/util/TestingUtils.java
deleted file mode 100644
index a7cc42b..0000000
--- a/src/com/android/launcher3/util/TestingUtils.java
+++ /dev/null
@@ -1,70 +0,0 @@
-package com.android.launcher3.util;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.util.Log;
-import android.view.Gravity;
-import android.view.View;
-import android.widget.FrameLayout;
-
-import com.android.launcher3.CustomAppWidget;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-
-import java.util.HashMap;
-
-public class TestingUtils {
-
-    public static final String MEMORY_TRACKER = "com.android.launcher3.testing.MemoryTracker";
-    public static final String ACTION_START_TRACKING = "com.android.launcher3.action.START_TRACKING";
-
-    public static final boolean MEMORY_DUMP_ENABLED = false;
-    public static final String SHOW_WEIGHT_WATCHER = "debug.show_mem";
-
-    public static final boolean ENABLE_CUSTOM_WIDGET_TEST = false;
-    public static final String DUMMY_WIDGET = "com.android.launcher3.testing.DummyWidget";
-
-    public static void startTrackingMemory(Context context) {
-        if (MEMORY_DUMP_ENABLED) {
-            context.startService(new Intent()
-                .setComponent(new ComponentName(context.getPackageName(), MEMORY_TRACKER))
-                .setAction(ACTION_START_TRACKING)
-                .putExtra("pid", android.os.Process.myPid())
-                .putExtra("name", "L"));
-        }
-    }
-
-    public static void addWeightWatcher(Launcher launcher) {
-        if (MEMORY_DUMP_ENABLED) {
-            boolean show = Utilities.getPrefs(launcher).getBoolean(SHOW_WEIGHT_WATCHER, true);
-
-            int id = launcher.getResources().getIdentifier("zzz_weight_watcher", "layout",
-                    launcher.getPackageName());
-            View watcher = launcher.getLayoutInflater().inflate(id, null);
-            watcher.setAlpha(0.5f);
-            ((FrameLayout) launcher.findViewById(R.id.launcher)).addView(watcher,
-                    new FrameLayout.LayoutParams(
-                            FrameLayout.LayoutParams.MATCH_PARENT,
-                            FrameLayout.LayoutParams.WRAP_CONTENT,
-                            Gravity.BOTTOM)
-            );
-
-            watcher.setVisibility(show ? View.VISIBLE : View.GONE);
-            launcher.mWeightWatcher = watcher;
-        }
-    }
-
-    public static void addDummyWidget(HashMap<String, CustomAppWidget> set) {
-        if (ENABLE_CUSTOM_WIDGET_TEST) {
-            try {
-                Class<?> clazz = Class.forName(DUMMY_WIDGET);
-                CustomAppWidget widget = (CustomAppWidget) clazz.newInstance();
-                set.put(widget.getClass().getName(), widget);
-            } catch (Exception e) {
-                Log.e("TestingUtils", "Error adding dummy widget", e);
-            }
-        }
-    }
-}
diff --git a/src/com/android/launcher3/util/TraceHelper.java b/src/com/android/launcher3/util/TraceHelper.java
new file mode 100644
index 0000000..5b66fcd
--- /dev/null
+++ b/src/com/android/launcher3/util/TraceHelper.java
@@ -0,0 +1,90 @@
+/*
+ * 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.util;
+
+import android.os.SystemClock;
+import android.os.Trace;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.MutableLong;
+
+import com.android.launcher3.config.FeatureFlags;
+
+/**
+ * A wrapper around {@link Trace} to allow easier proguarding for production builds.
+ *
+ * To enable any tracing log, execute the following command:
+ * $ adb shell setprop log.tag.TAGNAME VERBOSE
+ */
+public class TraceHelper {
+
+    private static final boolean ENABLED = FeatureFlags.IS_DOGFOOD_BUILD;
+
+    private static final boolean SYSTEM_TRACE = true;
+    private static final ArrayMap<String, MutableLong> sUpTimes =
+            ENABLED ? new ArrayMap<String, MutableLong>() : null;
+
+    public static void beginSection(String sectionName) {
+        if (ENABLED) {
+            MutableLong time = sUpTimes.get(sectionName);
+            if (time == null) {
+                time = new MutableLong(Log.isLoggable(sectionName, Log.VERBOSE) ? 0 : -1);
+                sUpTimes.put(sectionName, time);
+            }
+            if (time.value >= 0) {
+                if (SYSTEM_TRACE) {
+                    Trace.beginSection(sectionName);
+                }
+                time.value = SystemClock.uptimeMillis();
+            }
+        }
+    }
+
+    public static void partitionSection(String sectionName, String partition) {
+        if (ENABLED) {
+            MutableLong time = sUpTimes.get(sectionName);
+            if (time.value >= 0) {
+
+                if (SYSTEM_TRACE) {
+                    Trace.endSection();
+                    Trace.beginSection(sectionName);
+                }
+
+                long now = SystemClock.uptimeMillis();
+                Log.d(sectionName, partition + " : " + (now - time.value));
+                time.value = now;
+            }
+        }
+    }
+
+    public static void endSection(String sectionName) {
+        if (ENABLED) {
+            endSection(sectionName, "End");
+        }
+    }
+
+    public static void endSection(String sectionName, String msg) {
+        if (ENABLED) {
+            MutableLong time = sUpTimes.get(sectionName);
+            if (time.value >= 0) {
+                if (SYSTEM_TRACE) {
+                    Trace.endSection();
+                }
+                Log.d(sectionName, msg + " : " + (SystemClock.uptimeMillis() - time.value));
+            }
+        }
+    }
+}
diff --git a/src/com/android/launcher3/util/UiThreadHelper.java b/src/com/android/launcher3/util/UiThreadHelper.java
new file mode 100644
index 0000000..27140a1
--- /dev/null
+++ b/src/com/android/launcher3/util/UiThreadHelper.java
@@ -0,0 +1,76 @@
+/*
+ * 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.util;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Process;
+import android.view.inputmethod.InputMethodManager;
+
+/**
+ * Utility class for offloading some class from UI thread
+ */
+public class UiThreadHelper {
+
+    private static HandlerThread sHandlerThread;
+    private static Handler sHandler;
+
+    private static final int MSG_HIDE_KEYBOARD = 1;
+
+    public static Looper getBackgroundLooper() {
+        if (sHandlerThread == null) {
+            sHandlerThread =
+                    new HandlerThread("UiThreadHelper", Process.THREAD_PRIORITY_FOREGROUND);
+            sHandlerThread.start();
+        }
+        return sHandlerThread.getLooper();
+    }
+
+    private static Handler getHandler(Context context) {
+        if (sHandler == null) {
+            sHandler = new Handler(getBackgroundLooper(),
+                    new UiCallbacks(context.getApplicationContext()));
+        }
+        return sHandler;
+    }
+
+    public static void hideKeyboardAsync(Context context, IBinder token) {
+        Message.obtain(getHandler(context), MSG_HIDE_KEYBOARD, token).sendToTarget();
+    }
+
+    private static class UiCallbacks implements Handler.Callback {
+
+        private final InputMethodManager mIMM;
+
+        UiCallbacks(Context context) {
+            mIMM = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
+        }
+
+        @Override
+        public boolean handleMessage(Message message) {
+            switch (message.what) {
+                case MSG_HIDE_KEYBOARD:
+                    mIMM.hideSoftInputFromWindow((IBinder) message.obj, 0);
+                    return true;
+            }
+            return false;
+        }
+    }
+}
diff --git a/src/com/android/launcher3/util/VerticalFlingDetector.java b/src/com/android/launcher3/util/VerticalFlingDetector.java
deleted file mode 100644
index 7236c2d..0000000
--- a/src/com/android/launcher3/util/VerticalFlingDetector.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2016 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 android.content.Context;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
-import android.view.View;
-import android.view.ViewConfiguration;
-
-public class VerticalFlingDetector implements View.OnTouchListener {
-
-    private static final float CUSTOM_SLOP_MULTIPLIER = 2.2f;
-    private static final int SEC_IN_MILLIS = 1000;
-
-    private VelocityTracker mVelocityTracker;
-    private float mMinimumFlingVelocity;
-    private float mMaximumFlingVelocity;
-    private float mDownX, mDownY;
-    private boolean mShouldCheckFling;
-    private double mCustomTouchSlop;
-
-    public VerticalFlingDetector(Context context) {
-        ViewConfiguration vc = ViewConfiguration.get(context);
-        mMinimumFlingVelocity = vc.getScaledMinimumFlingVelocity();
-        mMaximumFlingVelocity = vc.getScaledMaximumFlingVelocity();
-        mCustomTouchSlop = CUSTOM_SLOP_MULTIPLIER * vc.getScaledTouchSlop();
-    }
-
-    @Override
-    public boolean onTouch(View v, MotionEvent ev) {
-        if (mVelocityTracker == null) {
-            mVelocityTracker = VelocityTracker.obtain();
-        }
-        mVelocityTracker.addMovement(ev);
-        switch (ev.getAction()) {
-            case MotionEvent.ACTION_DOWN:
-                mDownX = ev.getX();
-                mDownY = ev.getY();
-                mShouldCheckFling = false;
-                break;
-            case MotionEvent.ACTION_MOVE:
-                if (mShouldCheckFling) {
-                    break;
-                }
-                if (Math.abs(ev.getY() - mDownY) > mCustomTouchSlop &&
-                        Math.abs(ev.getY() - mDownY) > Math.abs(ev.getX() - mDownX)) {
-                    mShouldCheckFling = true;
-                }
-                break;
-            case MotionEvent.ACTION_UP:
-                if (mShouldCheckFling) {
-                    mVelocityTracker.computeCurrentVelocity(SEC_IN_MILLIS, mMaximumFlingVelocity);
-                    // only when fling is detected in down direction
-                    if (mVelocityTracker.getYVelocity() > mMinimumFlingVelocity) {
-                        cleanUp();
-                        return true;
-                    }
-                }
-                // fall through.
-            case MotionEvent.ACTION_CANCEL:
-                cleanUp();
-        }
-        return false;
-    }
-
-    private void cleanUp() {
-        if (mVelocityTracker == null) {
-            return;
-        }
-        mVelocityTracker.recycle();
-        mVelocityTracker = null;
-    }
-}
diff --git a/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java b/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java
index 3926974..5c24687 100644
--- a/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java
+++ b/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java
@@ -1,72 +1,48 @@
 package com.android.launcher3.util;
 
 import android.app.WallpaperManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
 import android.os.IBinder;
+import android.os.Message;
+import android.os.SystemClock;
 import android.util.Log;
-import android.view.Choreographer;
-import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
 
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
+import com.android.launcher3.anim.Interpolators;
 
 /**
  * Utility class to handle wallpaper scrolling along with workspace.
  */
-public class WallpaperOffsetInterpolator implements Choreographer.FrameCallback {
+public class WallpaperOffsetInterpolator extends BroadcastReceiver {
+
+    private static final int[] sTempInt = new int[2];
     private static final String TAG = "WPOffsetInterpolator";
     private static final int ANIMATION_DURATION = 250;
 
     // Don't use all the wallpaper for parallax until you have at least this many pages
     private static final int MIN_PARALLAX_PAGE_SPAN = 4;
 
-    private final Choreographer mChoreographer;
-    private final Interpolator mInterpolator;
-    private final WallpaperManager mWallpaperManager;
     private final Workspace mWorkspace;
     private final boolean mIsRtl;
+    private final Handler mHandler;
 
+    private boolean mRegistered = false;
     private IBinder mWindowToken;
     private boolean mWallpaperIsLiveWallpaper;
-    private float mLastSetWallpaperOffsetSteps = 0;
 
-    private float mFinalOffset = 0.0f;
-    private float mCurrentOffset = 0.5f; // to force an initial update
-    private boolean mWaitingForUpdate;
     private boolean mLockedToDefaultPage;
-
-    private boolean mAnimating;
-    private long mAnimationStartTime;
-    private float mAnimationStartOffset;
-    int mNumScreens;
-    int mNumPagesForWallpaperParallax;
+    private int mNumScreens;
 
     public WallpaperOffsetInterpolator(Workspace workspace) {
-        mChoreographer = Choreographer.getInstance();
-        mInterpolator = new DecelerateInterpolator(1.5f);
-
         mWorkspace = workspace;
-        mWallpaperManager = WallpaperManager.getInstance(workspace.getContext());
         mIsRtl = Utilities.isRtl(workspace.getResources());
-    }
-
-    @Override
-    public void doFrame(long frameTimeNanos) {
-        updateOffset(false);
-    }
-
-    private void updateOffset(boolean force) {
-        if (mWaitingForUpdate || force) {
-            mWaitingForUpdate = false;
-            if (computeScrollOffset() && mWindowToken != null) {
-                try {
-                    mWallpaperManager.setWallpaperOffsets(mWindowToken, getCurrX(), 0.5f);
-                    setWallpaperOffsetSteps();
-                } catch (IllegalArgumentException e) {
-                    Log.e(TAG, "Error updating wallpaper offset: " + e);
-                }
-            }
-        }
+        mHandler = new OffsetHandler(workspace.getContext());
     }
 
     /**
@@ -80,55 +56,34 @@
         return mLockedToDefaultPage;
     }
 
-    public boolean computeScrollOffset() {
-        final float oldOffset = mCurrentOffset;
-        if (mAnimating) {
-            long durationSinceAnimation = System.currentTimeMillis() - mAnimationStartTime;
-            float t0 = durationSinceAnimation / (float) ANIMATION_DURATION;
-            float t1 = mInterpolator.getInterpolation(t0);
-            mCurrentOffset = mAnimationStartOffset +
-                    (mFinalOffset - mAnimationStartOffset) * t1;
-            mAnimating = durationSinceAnimation < ANIMATION_DURATION;
-        } else {
-            mCurrentOffset = mFinalOffset;
-        }
-
-        if (Math.abs(mCurrentOffset - mFinalOffset) > 0.0000001f) {
-            scheduleUpdate();
-        }
-        if (Math.abs(oldOffset - mCurrentOffset) > 0.0000001f) {
-            return true;
-        }
-        return false;
-    }
-
     /**
+     * Computes the wallpaper offset as an int ratio (out[0] / out[1])
+     *
      * TODO: do different behavior if it's  a live wallpaper?
      */
-    public float wallpaperOffsetForScroll(int scroll) {
+    private void wallpaperOffsetForScroll(int scroll, int numScrollingPages, final int[] out) {
+        out[1] = 1;
+
         // To match the default wallpaper behavior in the system, we default to either the left
         // or right edge on initialization
-        int numScrollingPages = getNumScreensExcludingEmptyAndCustom();
         if (mLockedToDefaultPage || numScrollingPages <= 1) {
-            return mIsRtl ? 1f : 0f;
+            out[0] =  mIsRtl ? 1 : 0;
+            return;
         }
 
         // Distribute the wallpaper parallax over a minimum of MIN_PARALLAX_PAGE_SPAN workspace
         // screens, not including the custom screen, and empty screens (if > MIN_PARALLAX_PAGE_SPAN)
-        if (mWallpaperIsLiveWallpaper) {
-            mNumPagesForWallpaperParallax = numScrollingPages;
-        } else {
-            mNumPagesForWallpaperParallax = Math.max(MIN_PARALLAX_PAGE_SPAN, numScrollingPages);
-        }
+        int numPagesForWallpaperParallax = mWallpaperIsLiveWallpaper ? numScrollingPages :
+                        Math.max(MIN_PARALLAX_PAGE_SPAN, numScrollingPages);
 
         // Offset by the custom screen
         int leftPageIndex;
         int rightPageIndex;
         if (mIsRtl) {
-            rightPageIndex = mWorkspace.numCustomPages();
+            rightPageIndex = 0;
             leftPageIndex = rightPageIndex + numScrollingPages - 1;
         } else {
-            leftPageIndex = mWorkspace.numCustomPages();
+            leftPageIndex = 0;
             rightPageIndex = leftPageIndex + numScrollingPages - 1;
         }
 
@@ -136,106 +91,184 @@
         int leftPageScrollX = mWorkspace.getScrollForPage(leftPageIndex);
         int rightPageScrollX = mWorkspace.getScrollForPage(rightPageIndex);
         int scrollRange = rightPageScrollX - leftPageScrollX;
-        if (scrollRange == 0) {
-            return 0f;
+        if (scrollRange <= 0) {
+            out[0] = 0;
+            return;
         }
 
         // Sometimes the left parameter of the pages is animated during a layout transition;
         // this parameter offsets it to keep the wallpaper from animating as well
         int adjustedScroll = scroll - leftPageScrollX -
                 mWorkspace.getLayoutTransitionOffsetForPage(0);
-        float offset = Utilities.boundToRange((float) adjustedScroll / scrollRange, 0f, 1f);
+        adjustedScroll = Utilities.boundToRange(adjustedScroll, 0, scrollRange);
+        out[1] = (numPagesForWallpaperParallax - 1) * scrollRange;
 
         // The offset is now distributed 0..1 between the left and right pages that we care about,
         // so we just map that between the pages that we are using for parallax
-        float rtlOffset = 0;
+        int rtlOffset = 0;
         if (mIsRtl) {
             // In RTL, the pages are right aligned, so adjust the offset from the end
-            rtlOffset = (float) ((mNumPagesForWallpaperParallax - 1) - (numScrollingPages - 1)) /
-                    (mNumPagesForWallpaperParallax - 1);
+            rtlOffset = out[1] - (numScrollingPages - 1) * scrollRange;
         }
-        return rtlOffset + offset *
-                ((float) (numScrollingPages - 1) / (mNumPagesForWallpaperParallax - 1));
+        out[0] = rtlOffset + adjustedScroll * (numScrollingPages - 1);
     }
 
-    private float wallpaperOffsetForCurrentScroll() {
-        return wallpaperOffsetForScroll(mWorkspace.getScrollX());
+    public float wallpaperOffsetForScroll(int scroll) {
+        wallpaperOffsetForScroll(scroll, getNumScreensExcludingEmpty(), sTempInt);
+        return ((float) sTempInt[0]) / sTempInt[1];
     }
 
-    private int numEmptyScreensToIgnore() {
-        int numScrollingPages = mWorkspace.getChildCount() - mWorkspace.numCustomPages();
+    private int getNumScreensExcludingEmpty() {
+        int numScrollingPages = mWorkspace.getChildCount();
         if (numScrollingPages >= MIN_PARALLAX_PAGE_SPAN && mWorkspace.hasExtraEmptyScreen()) {
-            return 1;
+            return numScrollingPages - 1;
         } else {
-            return 0;
+            return numScrollingPages;
         }
     }
 
-    private int getNumScreensExcludingEmptyAndCustom() {
-        return mWorkspace.getChildCount() - numEmptyScreensToIgnore() - mWorkspace.numCustomPages();
-    }
-
     public void syncWithScroll() {
-        float offset = wallpaperOffsetForCurrentScroll();
-        setFinalX(offset);
-        updateOffset(true);
-    }
-
-    public float getCurrX() {
-        return mCurrentOffset;
-    }
-
-    public float getFinalX() {
-        return mFinalOffset;
-    }
-
-    private void animateToFinal() {
-        mAnimating = true;
-        mAnimationStartOffset = mCurrentOffset;
-        mAnimationStartTime = System.currentTimeMillis();
-    }
-
-    private void setWallpaperOffsetSteps() {
-        // Set wallpaper offset steps (1 / (number of screens - 1))
-        float xOffset = 1.0f / (mNumPagesForWallpaperParallax - 1);
-        if (xOffset != mLastSetWallpaperOffsetSteps) {
-            mWallpaperManager.setWallpaperOffsetSteps(xOffset, 1.0f);
-            mLastSetWallpaperOffsetSteps = xOffset;
-        }
-    }
-
-    public void setFinalX(float x) {
-        scheduleUpdate();
-        mFinalOffset = Math.max(0f, Math.min(x, 1f));
-        if (getNumScreensExcludingEmptyAndCustom() != mNumScreens) {
-            if (mNumScreens > 0 && Float.compare(mCurrentOffset, mFinalOffset) != 0) {
-                // Don't animate if we're going from 0 screens, or if the final offset is the same
-                // as the current offset
-                animateToFinal();
+        int numScreens = getNumScreensExcludingEmpty();
+        wallpaperOffsetForScroll(mWorkspace.getScrollX(), numScreens, sTempInt);
+        Message msg = Message.obtain(mHandler, MSG_UPDATE_OFFSET, sTempInt[0], sTempInt[1],
+                mWindowToken);
+        if (numScreens != mNumScreens) {
+            if (mNumScreens > 0) {
+                // Don't animate if we're going from 0 screens
+                msg.what = MSG_START_ANIMATION;
             }
-            mNumScreens = getNumScreensExcludingEmptyAndCustom();
+            mNumScreens = numScreens;
+            updateOffset();
         }
+        msg.sendToTarget();
     }
 
-    private void scheduleUpdate() {
-        if (!mWaitingForUpdate) {
-            mChoreographer.postFrameCallback(this);
-            mWaitingForUpdate = true;
+    private void updateOffset() {
+        int numPagesForWallpaperParallax;
+        if (mWallpaperIsLiveWallpaper) {
+            numPagesForWallpaperParallax = mNumScreens;
+        } else {
+            numPagesForWallpaperParallax = Math.max(MIN_PARALLAX_PAGE_SPAN, mNumScreens);
         }
+        Message.obtain(mHandler, MSG_SET_NUM_PARALLAX, numPagesForWallpaperParallax, 0,
+                mWindowToken).sendToTarget();
     }
 
     public void jumpToFinal() {
-        mCurrentOffset = mFinalOffset;
-    }
-
-    public void onResume() {
-        mWallpaperIsLiveWallpaper = mWallpaperManager.getWallpaperInfo() != null;
-        // Force the wallpaper offset steps to be set again, because another app might have changed
-        // them
-        mLastSetWallpaperOffsetSteps = 0f;
+        Message.obtain(mHandler, MSG_JUMP_TO_FINAL, mWindowToken).sendToTarget();
     }
 
     public void setWindowToken(IBinder token) {
         mWindowToken = token;
+        if (mWindowToken == null && mRegistered) {
+            mWorkspace.getContext().unregisterReceiver(this);
+            mRegistered = false;
+        } else if (mWindowToken != null && !mRegistered) {
+            mWorkspace.getContext()
+                    .registerReceiver(this, new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED));
+            onReceive(mWorkspace.getContext(), null);
+            mRegistered = true;
+        }
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        mWallpaperIsLiveWallpaper =
+                WallpaperManager.getInstance(mWorkspace.getContext()).getWallpaperInfo() != null;
+        updateOffset();
+    }
+
+    private static final int MSG_START_ANIMATION = 1;
+    private static final int MSG_UPDATE_OFFSET = 2;
+    private static final int MSG_APPLY_OFFSET = 3;
+    private static final int MSG_SET_NUM_PARALLAX = 4;
+    private static final int MSG_JUMP_TO_FINAL = 5;
+
+    private static class OffsetHandler extends Handler {
+
+        private final Interpolator mInterpolator;
+        private final WallpaperManager mWM;
+
+        private float mCurrentOffset = 0.5f; // to force an initial update
+        private boolean mAnimating;
+        private long mAnimationStartTime;
+        private float mAnimationStartOffset;
+
+        private float mFinalOffset;
+        private float mOffsetX;
+
+        public OffsetHandler(Context context) {
+            super(UiThreadHelper.getBackgroundLooper());
+            mInterpolator = Interpolators.DEACCEL_1_5;
+            mWM = WallpaperManager.getInstance(context);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            final IBinder token = (IBinder) msg.obj;
+            if (token == null) {
+                return;
+            }
+
+            switch (msg.what) {
+                case MSG_START_ANIMATION: {
+                    mAnimating = true;
+                    mAnimationStartOffset = mCurrentOffset;
+                    mAnimationStartTime = msg.getWhen();
+                    // Follow through
+                }
+                case MSG_UPDATE_OFFSET:
+                    mFinalOffset = ((float) msg.arg1) / msg.arg2;
+                    // Follow through
+                case MSG_APPLY_OFFSET: {
+                    float oldOffset = mCurrentOffset;
+                    if (mAnimating) {
+                        long durationSinceAnimation = SystemClock.uptimeMillis()
+                                - mAnimationStartTime;
+                        float t0 = durationSinceAnimation / (float) ANIMATION_DURATION;
+                        float t1 = mInterpolator.getInterpolation(t0);
+                        mCurrentOffset = mAnimationStartOffset +
+                                (mFinalOffset - mAnimationStartOffset) * t1;
+                        mAnimating = durationSinceAnimation < ANIMATION_DURATION;
+                    } else {
+                        mCurrentOffset = mFinalOffset;
+                    }
+
+                    if (Float.compare(mCurrentOffset, oldOffset) != 0) {
+                        setOffsetSafely(token);
+                        // Force the wallpaper offset steps to be set again, because another app
+                        // might have changed them
+                        mWM.setWallpaperOffsetSteps(mOffsetX, 1.0f);
+                    }
+                    if (mAnimating) {
+                        // If we are animating, keep updating the offset
+                        Message.obtain(this, MSG_APPLY_OFFSET, token).sendToTarget();
+                    }
+                    return;
+                }
+                case MSG_SET_NUM_PARALLAX: {
+                    // Set wallpaper offset steps (1 / (number of screens - 1))
+                    mOffsetX = 1.0f / (msg.arg1 - 1);
+                    mWM.setWallpaperOffsetSteps(mOffsetX, 1.0f);
+                    return;
+                }
+                case MSG_JUMP_TO_FINAL: {
+                    if (Float.compare(mCurrentOffset, mFinalOffset) != 0) {
+                        mCurrentOffset = mFinalOffset;
+                        setOffsetSafely(token);
+                    }
+                    mAnimating = false;
+                    return;
+                }
+            }
+        }
+
+        private void setOffsetSafely(IBinder token) {
+            try {
+                mWM.setWallpaperOffsets(token, mCurrentOffset, 0.5f);
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "Error updating wallpaper offset: " + e);
+            }
+        }
     }
 }
\ No newline at end of file
diff --git a/src/com/android/launcher3/views/RecyclerViewFastScroller.java b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
index 7b5bcdb..8f20a8d 100644
--- a/src/com/android/launcher3/views/RecyclerViewFastScroller.java
+++ b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
@@ -98,6 +98,7 @@
     private String mPopupSectionName;
 
     protected BaseRecyclerView mRv;
+    private RecyclerView.OnScrollListener mOnScrollListener;
 
     private int mDownX;
     private int mDownY;
@@ -141,7 +142,10 @@
 
     public void setRecyclerView(BaseRecyclerView rv, TextView popupView) {
         mRv = rv;
-        mRv.addOnScrollListener(new RecyclerView.OnScrollListener() {
+        if (mOnScrollListener != null) {
+            mRv.removeOnScrollListener(mOnScrollListener);
+        }
+        mRv.addOnScrollListener(mOnScrollListener = new RecyclerView.OnScrollListener() {
             @Override
             public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                 mDy = dy;
@@ -285,7 +289,7 @@
             return;
         }
         int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
-        canvas.translate(getWidth() / 2, mRv.getPaddingTop());
+        canvas.translate(getWidth() / 2, mRv.getScrollBarTop());
         // Draw the track
         float halfW = mWidth / 2;
         canvas.drawRoundRect(-halfW, 0, halfW, mRv.getScrollbarTrackHeight(),
@@ -317,7 +321,7 @@
      * Returns whether the specified point is inside the thumb bounds.
      */
     private boolean isNearThumb(int x, int y) {
-        int offset = y - mRv.getPaddingTop() - mThumbOffsetY;
+        int offset = y - mRv.getScrollBarTop() - mThumbOffsetY;
 
         return x >= 0 && x < getWidth() && offset >= 0 && offset <= mThumbHeight;
     }
@@ -348,7 +352,7 @@
     private void updatePopupY(int lastTouchY) {
         int height = mPopupView.getHeight();
         float top = lastTouchY - (FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR * height)
-                + mRv.getPaddingTop();
+                + mRv.getScrollBarTop();
         top = Utilities.boundToRange(top,
                 mMaxWidth, mRv.getScrollbarTrackHeight() - mMaxWidth - height);
         mPopupView.setTranslationY(top);
diff --git a/src/com/android/launcher3/views/SlidingTabStrip.java b/src/com/android/launcher3/views/SlidingTabStrip.java
new file mode 100644
index 0000000..45c6261
--- /dev/null
+++ b/src/com/android/launcher3/views/SlidingTabStrip.java
@@ -0,0 +1,107 @@
+/*
+ * 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.views;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.Button;
+import android.widget.LinearLayout;
+
+import com.android.launcher3.R;
+import com.android.launcher3.util.Themes;
+
+public class SlidingTabStrip extends LinearLayout {
+
+    private final Paint mSelectedIndicatorPaint;
+    private int mSelectedIndicatorHeight;
+    private int mIndicatorLeft = -1;
+    private int mIndicatorRight = -1;
+    private int mSelectedPosition = -1;
+    private float mSelectionOffset;
+
+    public SlidingTabStrip(@NonNull Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+        setOrientation(HORIZONTAL);
+        setWillNotDraw(false);
+        mSelectedIndicatorPaint = new Paint();
+        mSelectedIndicatorPaint.setColor(Themes.getAttrColor(context, android.R.attr.colorAccent));
+        mSelectedIndicatorHeight = getResources()
+                .getDimensionPixelSize(R.dimen.all_apps_tabs_indicator_height);
+    }
+
+    public void updateIndicatorPosition(int position, float positionOffset) {
+        mSelectedPosition = position;
+        mSelectionOffset = positionOffset;
+        updateIndicatorPosition();
+    }
+
+    public void updateTabTextColor(int pos) {
+        for (int i=0; i < getChildCount(); i++) {
+            Button tab = (Button) getChildAt(i);
+            tab.setSelected(i == pos);
+        }
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        super.onLayout(changed, l, t, r, b);
+        updateTabTextColor(0);
+        updateIndicatorPosition(0, 0);
+    }
+
+    private void updateIndicatorPosition() {
+        final View tab = getChildAt(mSelectedPosition);
+        int left, right;
+
+        if (tab != null && tab.getWidth() > 0) {
+            left = tab.getLeft();
+            right = tab.getRight();
+
+            if (mSelectionOffset > 0f && mSelectedPosition < getChildCount() - 1) {
+                // Draw the selection partway between the tabs
+                View nextTitle = getChildAt(mSelectedPosition + 1);
+                left = (int) (mSelectionOffset * nextTitle.getLeft() +
+                        (1.0f - mSelectionOffset) * left);
+                right = (int) (mSelectionOffset * nextTitle.getRight() +
+                        (1.0f - mSelectionOffset) * right);
+            }
+        } else {
+            left = right = -1;
+        }
+
+        setIndicatorPosition(left, right);
+    }
+
+    private void setIndicatorPosition(int left, int right) {
+        if (left != mIndicatorLeft || right != mIndicatorRight) {
+            mIndicatorLeft = left;
+            mIndicatorRight = right;
+            invalidate();
+        }
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+        canvas.drawRect(mIndicatorLeft, getHeight() - mSelectedIndicatorHeight,
+                mIndicatorRight, getHeight(), mSelectedIndicatorPaint);
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
new file mode 100644
index 0000000..e328759
--- /dev/null
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -0,0 +1,280 @@
+/*
+ * 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.widget;
+
+import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
+import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.content.Context;
+import android.graphics.Point;
+import android.util.AttributeSet;
+import android.util.Property;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnLongClickListener;
+import android.view.animation.Interpolator;
+import android.widget.Toast;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.DragSource;
+import com.android.launcher3.DropTarget.DragObject;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAnimUtils;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.graphics.GradientView;
+import com.android.launcher3.touch.SwipeDetector;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
+import com.android.launcher3.util.SystemUiController;
+import com.android.launcher3.util.Themes;
+
+/**
+ * Base class for various widgets popup
+ */
+abstract class BaseWidgetSheet extends AbstractFloatingView
+        implements OnClickListener, OnLongClickListener, DragSource, SwipeDetector.Listener {
+
+
+    protected static Property<BaseWidgetSheet, Float> TRANSLATION_SHIFT =
+            new Property<BaseWidgetSheet, Float>(Float.class, "translationShift") {
+
+                @Override
+                public Float get(BaseWidgetSheet view) {
+                    return view.mTranslationShift;
+                }
+
+                @Override
+                public void set(BaseWidgetSheet view, Float value) {
+                    view.setTranslationShift(value);
+                }
+            };
+    protected static final float TRANSLATION_SHIFT_CLOSED = 1f;
+    protected static final float TRANSLATION_SHIFT_OPENED = 0f;
+
+    /* Touch handling related member variables. */
+    private Toast mWidgetInstructionToast;
+
+    protected final Launcher mLauncher;
+    protected final SwipeDetector mSwipeDetector;
+    protected final ObjectAnimator mOpenCloseAnimator;
+
+    protected View mContent;
+    protected GradientView mGradientView;
+    protected Interpolator mScrollInterpolator;
+
+    // range [0, 1], 0=> completely open, 1=> completely closed
+    protected float mTranslationShift = TRANSLATION_SHIFT_CLOSED;
+
+    protected boolean mNoIntercept;
+
+    public BaseWidgetSheet(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        mLauncher = Launcher.getLauncher(context);
+
+        mScrollInterpolator = Interpolators.SCROLL_CUBIC;
+        mSwipeDetector = new SwipeDetector(context, this, SwipeDetector.VERTICAL);
+
+        mOpenCloseAnimator = LauncherAnimUtils.ofPropertyValuesHolder(this);
+        mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mSwipeDetector.finishedScrolling();
+            }
+        });
+    }
+
+    @Override
+    public final void onClick(View v) {
+        // Let the user know that they have to long press to add a widget
+        if (mWidgetInstructionToast != null) {
+            mWidgetInstructionToast.cancel();
+        }
+
+        CharSequence msg = Utilities.wrapForTts(
+                getContext().getText(R.string.long_press_widget_to_add),
+                getContext().getString(R.string.long_accessible_way_to_add));
+        mWidgetInstructionToast = Toast.makeText(getContext(), msg, Toast.LENGTH_SHORT);
+        mWidgetInstructionToast.show();
+    }
+
+    @Override
+    public final boolean onLongClick(View v) {
+        if (!mLauncher.isDraggingEnabled()) return false;
+
+        if (v instanceof WidgetCell) {
+            return beginDraggingWidget((WidgetCell) v);
+        }
+        return true;
+    }
+
+    protected void setTranslationShift(float translationShift) {
+        mTranslationShift = translationShift;
+        mGradientView.setAlpha(1 - mTranslationShift);
+        mContent.setTranslationY(mTranslationShift * mContent.getHeight());
+    }
+
+    private boolean beginDraggingWidget(WidgetCell v) {
+        // Get the widget preview as the drag representation
+        WidgetImageView image = v.getWidgetView();
+
+        // If the ImageView doesn't have a drawable yet, the widget preview hasn't been loaded and
+        // we abort the drag.
+        if (image.getBitmap() == null) {
+            return false;
+        }
+
+        int[] loc = new int[2];
+        mLauncher.getDragLayer().getLocationInDragLayer(image, loc);
+
+        new PendingItemDragHelper(v).startDrag(
+                image.getBitmapBounds(), image.getBitmap().getWidth(), image.getWidth(),
+                new Point(loc[0], loc[1]), this, new DragOptions());
+        close(true);
+        return true;
+    }
+
+    //
+    // Drag related handling methods that implement {@link DragSource} interface.
+    //
+
+    @Override
+    public void onDropCompleted(View target, DragObject d, boolean success) { }
+
+
+    @Override
+    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+        if (ev.getAction() == MotionEvent.ACTION_UP && !mNoIntercept) {
+            // If we got ACTION_UP without ever returning true on intercept,
+            // the user never started dragging the bottom sheet.
+            if (!mLauncher.getDragLayer().isEventOverView(mContent, ev)) {
+                close(true);
+                return false;
+            }
+        }
+
+        if (mNoIntercept) {
+            return false;
+        }
+
+        int directionsToDetectScroll = mSwipeDetector.isIdleState() ?
+                SwipeDetector.DIRECTION_NEGATIVE : 0;
+        mSwipeDetector.setDetectableScrollConditions(
+                directionsToDetectScroll, false);
+        mSwipeDetector.onTouchEvent(ev);
+        return mSwipeDetector.isDraggingOrSettling();
+    }
+
+    @Override
+    public boolean onControllerTouchEvent(MotionEvent ev) {
+        return mSwipeDetector.onTouchEvent(ev);
+    }
+
+    /* SwipeDetector.Listener */
+
+    @Override
+    public void onDragStart(boolean start) { }
+
+    @Override
+    public boolean onDrag(float displacement, float velocity) {
+        float range = mContent.getHeight();
+        displacement = Utilities.boundToRange(displacement, 0, range);
+        setTranslationShift(displacement / range);
+        return true;
+    }
+
+    @Override
+    public void onDragEnd(float velocity, boolean fling) {
+        if ((fling && velocity > 0) || mTranslationShift > 0.5f) {
+            mScrollInterpolator = scrollInterpolatorForVelocity(velocity);
+            mOpenCloseAnimator.setDuration(SwipeDetector.calculateDuration(
+                    velocity, TRANSLATION_SHIFT_CLOSED - mTranslationShift));
+            close(true);
+        } else {
+            mOpenCloseAnimator.setValues(PropertyValuesHolder.ofFloat(
+                    TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
+            mOpenCloseAnimator.setDuration(
+                    SwipeDetector.calculateDuration(velocity, mTranslationShift))
+                    .setInterpolator(Interpolators.DEACCEL);
+            mOpenCloseAnimator.start();
+        }
+    }
+
+    protected void handleClose(boolean animate, long defaultDuration) {
+        if (!mIsOpen || mOpenCloseAnimator.isRunning()) {
+            return;
+        }
+        if (animate) {
+            mOpenCloseAnimator.setValues(
+                    PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_CLOSED));
+            mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    onCloseComplete();
+                }
+            });
+            if (mSwipeDetector.isIdleState()) {
+                mOpenCloseAnimator
+                        .setDuration(defaultDuration)
+                        .setInterpolator(Interpolators.ACCEL);
+            } else {
+                mOpenCloseAnimator.setInterpolator(mScrollInterpolator);
+            }
+            mOpenCloseAnimator.start();
+        } else {
+            setTranslationShift(TRANSLATION_SHIFT_CLOSED);
+            onCloseComplete();
+        }
+    }
+
+    protected void onCloseComplete() {
+        mIsOpen = false;
+        mLauncher.getDragLayer().removeView(this);
+        mLauncher.getSystemUiController().updateUiState(
+                SystemUiController.UI_STATE_WIDGET_BOTTOM_SHEET, 0);
+    }
+
+    protected void setupNavBarColor() {
+        boolean isSheetDark = Themes.getAttrBoolean(mLauncher, R.attr.isMainColorDark);
+        mLauncher.getSystemUiController().updateUiState(
+                SystemUiController.UI_STATE_WIDGET_BOTTOM_SHEET,
+                isSheetDark ? SystemUiController.FLAG_DARK_NAV : SystemUiController.FLAG_LIGHT_NAV);
+    }
+
+    @Override
+    public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
+        targetParent.containerType = ContainerType.WIDGETS;
+        targetParent.cardinality = getElementsRowCount();
+    }
+
+    @Override
+    public final void logActionCommand(int command) {
+        Target target = newContainerTarget(ContainerType.WIDGETS);
+        target.cardinality = getElementsRowCount();
+        mLauncher.getUserEventDispatcher().logActionCommand(command, target);
+    }
+
+    protected abstract int getElementsRowCount();
+
+}
diff --git a/src/com/android/launcher3/widget/PendingAddWidgetInfo.java b/src/com/android/launcher3/widget/PendingAddWidgetInfo.java
index ad05ce9..bc40484 100644
--- a/src/com/android/launcher3/widget/PendingAddWidgetInfo.java
+++ b/src/com/android/launcher3/widget/PendingAddWidgetInfo.java
@@ -35,13 +35,13 @@
     public Bundle bindOptions = null;
 
     public PendingAddWidgetInfo(LauncherAppWidgetProviderInfo i) {
-        if (i.isCustomWidget) {
+        if (i.isCustomWidget()) {
             itemType = LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
         } else {
             itemType = LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
         }
         this.info = i;
-        user = i.getUser();
+        user = i.getProfile();
         componentName = i.provider;
         previewImage = i.previewImage;
         icon = i.icon;
diff --git a/src/com/android/launcher3/widget/PendingItemDragHelper.java b/src/com/android/launcher3/widget/PendingItemDragHelper.java
index 19be28d..c5cf5e2 100644
--- a/src/com/android/launcher3/widget/PendingItemDragHelper.java
+++ b/src/com/android/launcher3/widget/PendingItemDragHelper.java
@@ -22,7 +22,6 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
-import android.os.Build;
 import android.view.View;
 import android.widget.RemoteViews;
 
@@ -32,11 +31,9 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.PendingAddItemInfo;
 import com.android.launcher3.R;
-import com.android.launcher3.Workspace;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.dragndrop.LivePreviewWidgetCell;
 import com.android.launcher3.graphics.DragPreviewProvider;
-import com.android.launcher3.graphics.HolographicOutlineHelper;
 import com.android.launcher3.graphics.LauncherIcons;
 
 /**
@@ -48,8 +45,8 @@
     private static final float MAX_WIDGET_SCALE = 1.25f;
 
     private final PendingAddItemInfo mAddInfo;
+    private int[] mEstimatedCellSize;
 
-    private Bitmap mPreviewBitmap;
     private RemoteViews mPreview;
 
     public PendingItemDragHelper(View view) {
@@ -80,12 +77,12 @@
         final Point dragOffset;
         final Rect dragRegion;
 
+        mEstimatedCellSize = launcher.getWorkspace().estimateItemSize(mAddInfo);
 
         if (mAddInfo instanceof PendingAddWidgetInfo) {
             PendingAddWidgetInfo createWidgetInfo = (PendingAddWidgetInfo) mAddInfo;
-            int[] size = launcher.getWorkspace().estimateItemSize(createWidgetInfo, true, false);
 
-            int maxWidth = Math.min((int) (previewBitmapWidth * MAX_WIDGET_SCALE), size[0]);
+            int maxWidth = Math.min((int) (previewBitmapWidth * MAX_WIDGET_SCALE), mEstimatedCellSize[0]);
 
             int[] previewSizeBeforeScale = new int[1];
 
@@ -117,14 +114,12 @@
             PendingAddShortcutInfo createShortcutInfo = (PendingAddShortcutInfo) mAddInfo;
             Drawable icon = createShortcutInfo.activityInfo.getFullResIcon(app.getIconCache());
             preview = LauncherIcons.createScaledBitmapWithoutShadow(icon, launcher, 0);
-            mAddInfo.spanX = mAddInfo.spanY = 1;
             scale = ((float) launcher.getDeviceProfile().iconSizePx) / preview.getWidth();
 
             dragOffset = new Point(previewPadding / 2, previewPadding / 2);
 
             // Create a preview same as the workspace cell size and draw the icon at the
             // appropriate position.
-            int[] size = launcher.getWorkspace().estimateItemSize(mAddInfo, false, true);
             DeviceProfile dp = launcher.getDeviceProfile();
             int iconSize = dp.iconSizePx;
 
@@ -134,9 +129,10 @@
             previewBounds.top += padding;
 
             dragRegion = new Rect();
-            dragRegion.left = (size[0] - iconSize) / 2;
+            dragRegion.left = (mEstimatedCellSize[0] - iconSize) / 2;
             dragRegion.right = dragRegion.left + iconSize;
-            dragRegion.top = (size[1] - iconSize - dp.iconTextSizePx - dp.iconDrawablePaddingPx) / 2;
+            dragRegion.top = (mEstimatedCellSize[1]
+                    - iconSize - dp.iconTextSizePx - dp.iconDrawablePaddingPx) / 2;
             dragRegion.bottom = dragRegion.top + iconSize;
         }
 
@@ -149,60 +145,31 @@
         int dragLayerY = screenPos.y + previewBounds.top
                 + (int) ((scale * preview.getHeight() - preview.getHeight()) / 2);
 
-        mPreviewBitmap = preview;
         // Start the drag
         launcher.getDragController().startDrag(preview, dragLayerX, dragLayerY, source, mAddInfo,
                 dragOffset, dragRegion, scale, options);
     }
 
-
     @Override
-    public Bitmap createDragOutline(Canvas canvas) {
-        if (mAddInfo instanceof PendingAddShortcutInfo) {
-            int width = mPreviewBitmap.getWidth();
-            int height = mPreviewBitmap.getHeight();
-            Bitmap b = Bitmap.createBitmap(width + blurSizeOutline, height + blurSizeOutline,
-                    Bitmap.Config.ALPHA_8);
-            canvas.setBitmap(b);
-
-            Launcher launcher = Launcher.getLauncher(mView.getContext());
-            int size = launcher.getDeviceProfile().iconSizePx;
-
-            Rect src = new Rect(0, 0, mPreviewBitmap.getWidth(), mPreviewBitmap.getHeight());
-            Rect dst = new Rect(0, 0, size, size);
-            dst.offset(blurSizeOutline / 2, blurSizeOutline / 2);
-            canvas.drawBitmap(mPreviewBitmap, src, dst, new Paint(Paint.FILTER_BITMAP_FLAG));
-
-            HolographicOutlineHelper.getInstance(mView.getContext())
-                    .applyExpensiveOutlineWithBlur(b, canvas);
-
-            canvas.setBitmap(null);
-            return b;
+    protected Bitmap convertPreviewToAlphaBitmap(Bitmap preview) {
+        if (mAddInfo instanceof PendingAddShortcutInfo || mEstimatedCellSize == null) {
+            return super.convertPreviewToAlphaBitmap(preview);
         }
 
-        Workspace workspace = Launcher.getLauncher(mView.getContext()).getWorkspace();
-        int[] size = workspace.estimateItemSize(mAddInfo, false, false);
-
-        int w = size[0];
-        int h = size[1];
+        int w = mEstimatedCellSize[0];
+        int h = mEstimatedCellSize[1];
         final Bitmap b = Bitmap.createBitmap(w, h, Bitmap.Config.ALPHA_8);
-        canvas.setBitmap(b);
+        Rect src = new Rect(0, 0, preview.getWidth(), preview.getHeight());
 
-        Rect src = new Rect(0, 0, mPreviewBitmap.getWidth(), mPreviewBitmap.getHeight());
-        float scaleFactor = Math.min((w - blurSizeOutline) / (float) mPreviewBitmap.getWidth(),
-                (h - blurSizeOutline) / (float) mPreviewBitmap.getHeight());
-        int scaledWidth = (int) (scaleFactor * mPreviewBitmap.getWidth());
-        int scaledHeight = (int) (scaleFactor * mPreviewBitmap.getHeight());
+        float scaleFactor = Math.min((w - blurSizeOutline) / (float) preview.getWidth(),
+                (h - blurSizeOutline) / (float) preview.getHeight());
+        int scaledWidth = (int) (scaleFactor * preview.getWidth());
+        int scaledHeight = (int) (scaleFactor * preview.getHeight());
         Rect dst = new Rect(0, 0, scaledWidth, scaledHeight);
 
         // center the image
         dst.offset((w - scaledWidth) / 2, (h - scaledHeight) / 2);
-
-        canvas.drawBitmap(mPreviewBitmap, src, dst, null);
-        HolographicOutlineHelper.getInstance(mView.getContext())
-                .applyExpensiveOutlineWithBlur(b, canvas);
-        canvas.setBitmap(null);
-
+        new Canvas(b).drawBitmap(preview, src, dst, new Paint(Paint.FILTER_BITMAP_FLAG));
         return b;
     }
 }
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index 40dbd52..2ba55ab 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -75,6 +75,9 @@
     protected CancellationSignal mActiveRequest;
     private boolean mAnimatePreview = true;
 
+    private boolean mApplyBitmapDeferred = false;
+    private Bitmap mDeferredBitmap;
+
     protected final BaseActivity mActivity;
 
     public WidgetCell(Context context) {
@@ -150,15 +153,31 @@
         return mWidgetImage;
     }
 
+    /**
+     * Sets if applying bitmap preview should be deferred. The UI will still load the bitmap, but
+     * will not cause invalidate, so that when deferring is disabled later, all the bitmaps are
+     * ready.
+     * This prevents invalidates while the animation is running.
+     */
+    public void setApplyBitmapDeferred(boolean isDeferred) {
+        if (mApplyBitmapDeferred != isDeferred) {
+            mApplyBitmapDeferred = isDeferred;
+            if (!mApplyBitmapDeferred && mDeferredBitmap != null) {
+                applyPreview(mDeferredBitmap);
+                mDeferredBitmap = null;
+            }
+        }
+    }
+
     public void setAnimatePreview(boolean shouldAnimate) {
         mAnimatePreview = shouldAnimate;
     }
 
     public void applyPreview(Bitmap bitmap) {
-        applyPreview(bitmap, true);
-    }
-
-    public void applyPreview(Bitmap bitmap, boolean animate) {
+        if (mApplyBitmapDeferred) {
+            mDeferredBitmap = bitmap;
+            return;
+        }
         if (bitmap != null) {
             mWidgetImage.setBitmap(bitmap,
                     DrawableFactory.get(getContext()).getBadgeForUser(mItem.user, getContext()));
@@ -173,15 +192,11 @@
     }
 
     public void ensurePreview() {
-        ensurePreview(true);
-    }
-
-    public void ensurePreview(boolean animate) {
         if (mActiveRequest != null) {
             return;
         }
         mActiveRequest = mWidgetPreviewLoader.getPreview(
-                mItem, mPresetPreviewSize, mPresetPreviewSize, this, animate);
+                mItem, mPresetPreviewSize, mPresetPreviewSize, this);
     }
 
     @Override
diff --git a/src/com/android/launcher3/widget/WidgetHostViewLoader.java b/src/com/android/launcher3/widget/WidgetHostViewLoader.java
index 5eeea44..8dcdd44 100644
--- a/src/com/android/launcher3/widget/WidgetHostViewLoader.java
+++ b/src/com/android/launcher3/widget/WidgetHostViewLoader.java
@@ -85,7 +85,7 @@
     private boolean preloadWidget() {
         final LauncherAppWidgetProviderInfo pInfo = mInfo.info;
 
-        if (pInfo.isCustomWidget) {
+        if (pInfo.isCustomWidget()) {
             return false;
         }
         final Bundle options = getDefaultOptionsForWidget(mLauncher, mInfo);
@@ -129,7 +129,7 @@
                 mWidgetLoadingId = -1;
 
                 hostView.setVisibility(View.INVISIBLE);
-                int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(mInfo, false, true);
+                int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(mInfo);
                 // We want the first widget layout to be the correct size. This will be important
                 // for width size reporting to the AppWidgetManager.
                 DragLayer.LayoutParams lp = new DragLayer.LayoutParams(unScaledSize[0],
diff --git a/src/com/android/launcher3/widget/WidgetsBottomSheet.java b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
index 01101ac..7fa5ff0 100644
--- a/src/com/android/launcher3/widget/WidgetsBottomSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
@@ -16,63 +16,36 @@
 
 package com.android.launcher3.widget;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
 import android.content.Context;
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.view.Gravity;
 import android.view.LayoutInflater;
-import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
 import android.widget.TextView;
 
-import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.DropTarget;
 import com.android.launcher3.Insettable;
 import com.android.launcher3.ItemInfo;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.touch.SwipeDetector;
-import com.android.launcher3.anim.PropertyListBuilder;
-import com.android.launcher3.dragndrop.DragController;
-import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.graphics.GradientView;
 import com.android.launcher3.model.WidgetItem;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.util.PackageUserKey;
-import com.android.launcher3.util.SystemUiController;
-import com.android.launcher3.util.Themes;
-import com.android.launcher3.util.TouchController;
 
 import java.util.List;
 
 /**
  * Bottom sheet for the "Widgets" system shortcut in the long-press popup.
  */
-public class WidgetsBottomSheet extends AbstractFloatingView implements Insettable, TouchController,
-        SwipeDetector.Listener, View.OnClickListener, View.OnLongClickListener,
-        DragController.DragListener {
+public class WidgetsBottomSheet extends BaseWidgetSheet implements Insettable {
 
-    private int mTranslationYOpen;
-    private int mTranslationYClosed;
-    private float mTranslationYRange;
-
-    private Launcher mLauncher;
+    private static final int DEFAULT_CLOSE_DURATION = 200;
     private ItemInfo mOriginalItemInfo;
-    private ObjectAnimator mOpenCloseAnimator;
-    private Interpolator mFastOutSlowInInterpolator;
-    private SwipeDetector.ScrollInterpolator mScrollInterpolator;
     private Rect mInsets;
-    private SwipeDetector mSwipeDetector;
-    private GradientView mGradientBackground;
 
     public WidgetsBottomSheet(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
@@ -81,23 +54,18 @@
     public WidgetsBottomSheet(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
         setWillNotDraw(false);
-        mLauncher = Launcher.getLauncher(context);
-        mOpenCloseAnimator = LauncherAnimUtils.ofPropertyValuesHolder(this);
-        mFastOutSlowInInterpolator =
-                AnimationUtils.loadInterpolator(context, android.R.interpolator.fast_out_slow_in);
-        mScrollInterpolator = new SwipeDetector.ScrollInterpolator();
         mInsets = new Rect();
-        mSwipeDetector = new SwipeDetector(context, this, SwipeDetector.VERTICAL);
-        mGradientBackground = (GradientView) mLauncher.getLayoutInflater().inflate(
+
+        mGradientView = (GradientView) mLauncher.getLayoutInflater().inflate(
                 R.layout.gradient_bg, mLauncher.getDragLayer(), false);
+        mGradientView.setProgress(1, false);
+        mContent = this;
     }
 
     @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-        mTranslationYOpen = 0;
-        mTranslationYClosed = getMeasuredHeight();
-        mTranslationYRange = mTranslationYClosed - mTranslationYOpen;
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        super.onLayout(changed, l, t, r, b);
+        setTranslationShift(mTranslationShift);
     }
 
     public void populateAndShow(ItemInfo itemInfo) {
@@ -107,22 +75,21 @@
 
         onWidgetsBound();
 
-        mLauncher.getDragLayer().addView(mGradientBackground);
-        mGradientBackground.setVisibility(VISIBLE);
+        mLauncher.getDragLayer().addView(mGradientView);
         mLauncher.getDragLayer().addView(this);
-        measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
-        setTranslationY(mTranslationYClosed);
         mIsOpen = false;
         open(true);
     }
 
     @Override
     protected void onWidgetsBound() {
-        List<WidgetItem> widgets = mLauncher.getWidgetsForPackageUser(new PackageUserKey(
-                mOriginalItemInfo.getTargetComponent().getPackageName(), mOriginalItemInfo.user));
+        List<WidgetItem> widgets = mLauncher.getPopupDataProvider().getWidgetsForPackageUser(
+                new PackageUserKey(
+                        mOriginalItemInfo.getTargetComponent().getPackageName(),
+                        mOriginalItemInfo.user));
 
-        ViewGroup widgetRow = (ViewGroup) findViewById(R.id.widgets);
-        ViewGroup widgetCells = (ViewGroup) widgetRow.findViewById(R.id.widgets_cell_list);
+        ViewGroup widgetRow = findViewById(R.id.widgets);
+        ViewGroup widgetCells = widgetRow.findViewById(R.id.widgets_cell_list);
 
         widgetCells.removeAllViews();
 
@@ -168,72 +135,31 @@
         return widget;
     }
 
-    @Override
-    public void onClick(View view) {
-        mLauncher.getWidgetsView().handleClick();
-    }
-
-    @Override
-    public boolean onLongClick(View view) {
-        mLauncher.getDragController().addDragListener(this);
-        return mLauncher.getWidgetsView().handleLongClick(view);
-    }
-
     private void open(boolean animate) {
         if (mIsOpen || mOpenCloseAnimator.isRunning()) {
             return;
         }
         mIsOpen = true;
-        boolean isSheetDark = Themes.getAttrBoolean(mLauncher, R.attr.isMainColorDark);
-        mLauncher.getSystemUiController().updateUiState(
-                SystemUiController.UI_STATE_WIDGET_BOTTOM_SHEET,
-                isSheetDark ? SystemUiController.FLAG_DARK_NAV : SystemUiController.FLAG_LIGHT_NAV);
+        setupNavBarColor();
         if (animate) {
-            mOpenCloseAnimator.setValues(new PropertyListBuilder()
-                    .translationY(mTranslationYOpen).build());
-            mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    mSwipeDetector.finishedScrolling();
-                }
-            });
-            mOpenCloseAnimator.setInterpolator(mFastOutSlowInInterpolator);
+            mOpenCloseAnimator.setValues(
+                    PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
+            mOpenCloseAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
             mOpenCloseAnimator.start();
         } else {
-            setTranslationY(mTranslationYOpen);
+            setTranslationShift(TRANSLATION_SHIFT_OPENED);
         }
     }
 
     @Override
     protected void handleClose(boolean animate) {
-        if (!mIsOpen || mOpenCloseAnimator.isRunning()) {
-            return;
-        }
-        if (animate) {
-            mOpenCloseAnimator.setValues(new PropertyListBuilder()
-                    .translationY(mTranslationYClosed).build());
-            mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    mSwipeDetector.finishedScrolling();
-                    onCloseComplete();
-                }
-            });
-            mOpenCloseAnimator.setInterpolator(mSwipeDetector.isIdleState()
-                    ? mFastOutSlowInInterpolator : mScrollInterpolator);
-            mOpenCloseAnimator.start();
-        } else {
-            setTranslationY(mTranslationYClosed);
-            onCloseComplete();
-        }
+        handleClose(animate, DEFAULT_CLOSE_DURATION);
     }
 
-    private void onCloseComplete() {
-        mIsOpen = false;
-        mLauncher.getDragLayer().removeView(mGradientBackground);
-        mLauncher.getDragLayer().removeView(WidgetsBottomSheet.this);
-        mLauncher.getSystemUiController().updateUiState(
-                SystemUiController.UI_STATE_WIDGET_BOTTOM_SHEET, 0);
+    @Override
+    protected void onCloseComplete() {
+        super.onCloseComplete();
+        mLauncher.getDragLayer().removeView(mGradientView);
     }
 
     @Override
@@ -242,18 +168,6 @@
     }
 
     @Override
-    public int getLogContainerType() {
-        return LauncherLogProto.ContainerType.WIDGETS; // TODO: be more specific
-    }
-
-    /**
-     * Returns a {@link WidgetsBottomSheet} which is already open or null
-     */
-    public static WidgetsBottomSheet getOpen(Launcher launcher) {
-        return getOpenView(launcher, TYPE_WIDGETS_BOTTOM_SHEET);
-    }
-
-    @Override
     public void setInsets(Rect insets) {
         // Extend behind left, right, and bottom insets.
         int leftInset = insets.left - mInsets.left;
@@ -264,67 +178,8 @@
                 getPaddingRight() + rightInset, getPaddingBottom() + bottomInset);
     }
 
-    /* SwipeDetector.Listener */
-
     @Override
-    public void onDragStart(boolean start) {
-    }
-
-    @Override
-    public boolean onDrag(float displacement, float velocity) {
-        setTranslationY(Utilities.boundToRange(displacement, mTranslationYOpen,
-                mTranslationYClosed));
-        return true;
-    }
-
-    @Override
-    public void setTranslationY(float translationY) {
-        super.setTranslationY(translationY);
-        if (mGradientBackground == null) return;
-        float p = (mTranslationYClosed - translationY) / mTranslationYRange;
-        boolean showScrim = p <= 0;
-        mGradientBackground.setProgress(p, showScrim);
-    }
-
-    @Override
-    public void onDragEnd(float velocity, boolean fling) {
-        if ((fling && velocity > 0) || getTranslationY() > (mTranslationYRange) / 2) {
-            mScrollInterpolator.setVelocityAtZero(velocity);
-            mOpenCloseAnimator.setDuration(SwipeDetector.calculateDuration(velocity,
-                    (mTranslationYClosed - getTranslationY()) / mTranslationYRange));
-            close(true);
-        } else {
-            mIsOpen = false;
-            mOpenCloseAnimator.setDuration(SwipeDetector.calculateDuration(velocity,
-                    (getTranslationY() - mTranslationYOpen) / mTranslationYRange));
-            open(true);
-        }
-    }
-
-    @Override
-    public boolean onControllerTouchEvent(MotionEvent ev) {
-        return mSwipeDetector.onTouchEvent(ev);
-    }
-
-    @Override
-    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
-        int directionsToDetectScroll = mSwipeDetector.isIdleState() ?
-                SwipeDetector.DIRECTION_NEGATIVE : 0;
-        mSwipeDetector.setDetectableScrollConditions(
-                directionsToDetectScroll, false);
-        mSwipeDetector.onTouchEvent(ev);
-        return mSwipeDetector.isDraggingOrSettling();
-    }
-
-    /* DragListener */
-
-    @Override
-    public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
-        // A widget or custom shortcut was dragged.
-        close(true);
-    }
-
-    @Override
-    public void onDragEnd() {
+    protected int getElementsRowCount() {
+        return 1;
     }
 }
diff --git a/src/com/android/launcher3/widget/WidgetsContainerView.java b/src/com/android/launcher3/widget/WidgetsContainerView.java
deleted file mode 100644
index acec3dd..0000000
--- a/src/com/android/launcher3/widget/WidgetsContainerView.java
+++ /dev/null
@@ -1,262 +0,0 @@
-/*
- * Copyright (C) 2015 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.widget;
-
-import android.content.Context;
-import android.content.pm.LauncherApps;
-import android.graphics.Point;
-import android.support.v7.widget.LinearLayoutManager;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.Toast;
-
-import com.android.launcher3.BaseContainerView;
-import com.android.launcher3.DeleteDropTarget;
-import com.android.launcher3.DragSource;
-import com.android.launcher3.DropTarget.DragObject;
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.compat.AlphabeticIndexCompat;
-import com.android.launcher3.dragndrop.DragOptions;
-import com.android.launcher3.folder.Folder;
-import com.android.launcher3.model.PackageItemInfo;
-import com.android.launcher3.model.WidgetItem;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
-import com.android.launcher3.util.MultiHashMap;
-import com.android.launcher3.util.PackageUserKey;
-import com.android.launcher3.util.Thunk;
-
-import java.util.List;
-
-/**
- * The widgets list view container.
- */
-public class WidgetsContainerView extends BaseContainerView
-        implements View.OnLongClickListener, View.OnClickListener, DragSource {
-    private static final String TAG = "WidgetsContainerView";
-    private static final boolean LOGD = false;
-
-    /* Global instances that are used inside this container. */
-    @Thunk Launcher mLauncher;
-
-    /* Recycler view related member variables */
-    private WidgetsRecyclerView mRecyclerView;
-    private WidgetsListAdapter mAdapter;
-
-    /* Touch handling related member variables. */
-    private Toast mWidgetInstructionToast;
-
-    public WidgetsContainerView(Context context) {
-        this(context, null);
-    }
-
-    public WidgetsContainerView(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public WidgetsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-        mLauncher = Launcher.getLauncher(context);
-        LauncherAppState apps = LauncherAppState.getInstance(context);
-        mAdapter = new WidgetsListAdapter(context, LayoutInflater.from(context),
-                apps.getWidgetCache(), new AlphabeticIndexCompat(context), this, this,
-                new WidgetsDiffReporter(apps.getIconCache()));
-        mAdapter.setNotifyListener();
-        if (LOGD) {
-            Log.d(TAG, "WidgetsContainerView constructor");
-        }
-    }
-
-    @Override
-    public View getTouchDelegateTargetView() {
-        return mRecyclerView;
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        mRecyclerView = (WidgetsRecyclerView) getContentView().findViewById(R.id.widgets_list_view);
-        mRecyclerView.setAdapter(mAdapter);
-        mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
-    }
-
-    //
-    // Returns views used for launcher transitions.
-    //
-
-    public void scrollToTop() {
-        mRecyclerView.scrollToPosition(0);
-    }
-
-    //
-    // Touch related handling.
-    //
-
-    @Override
-    public void onClick(View v) {
-        // When we have exited widget tray or are in transition, disregard clicks
-        if (!mLauncher.isWidgetsViewVisible()
-                || mLauncher.getWorkspace().isSwitchingState()
-                || !(v instanceof WidgetCell)) return;
-
-        handleClick();
-    }
-
-    public void handleClick() {
-        // Let the user know that they have to long press to add a widget
-        if (mWidgetInstructionToast != null) {
-            mWidgetInstructionToast.cancel();
-        }
-
-        CharSequence msg = Utilities.wrapForTts(
-                getContext().getText(R.string.long_press_widget_to_add),
-                getContext().getString(R.string.long_accessible_way_to_add));
-        mWidgetInstructionToast = Toast.makeText(getContext(), msg, Toast.LENGTH_SHORT);
-        mWidgetInstructionToast.show();
-    }
-
-    @Override
-    public boolean onLongClick(View v) {
-        // When we have exited the widget tray, disregard long clicks
-        if (!mLauncher.isWidgetsViewVisible()) return false;
-        return handleLongClick(v);
-    }
-
-    public boolean handleLongClick(View v) {
-        if (LOGD) {
-            Log.d(TAG, String.format("onLongClick [v=%s]", v));
-        }
-        // When we  are in transition, disregard long clicks
-        if (mLauncher.getWorkspace().isSwitchingState()) return false;
-        // Return if global dragging is not enabled
-        if (!mLauncher.isDraggingEnabled()) return false;
-
-        return beginDragging(v);
-    }
-
-    private boolean beginDragging(View v) {
-        if (v instanceof WidgetCell) {
-            if (!beginDraggingWidget((WidgetCell) v)) {
-                return false;
-            }
-        } else {
-            Log.e(TAG, "Unexpected dragging view: " + v);
-        }
-
-        // We don't enter spring-loaded mode if the drag has been cancelled
-        if (mLauncher.getDragController().isDragging()) {
-            // Go into spring loaded mode (must happen before we startDrag())
-            mLauncher.enterSpringLoadedDragMode();
-        }
-
-        return true;
-    }
-
-    private boolean beginDraggingWidget(WidgetCell v) {
-        // Get the widget preview as the drag representation
-        WidgetImageView image = (WidgetImageView) v.findViewById(R.id.widget_preview);
-
-        // If the ImageView doesn't have a drawable yet, the widget preview hasn't been loaded and
-        // we abort the drag.
-        if (image.getBitmap() == null) {
-            return false;
-        }
-
-        int[] loc = new int[2];
-        mLauncher.getDragLayer().getLocationInDragLayer(image, loc);
-
-        new PendingItemDragHelper(v).startDrag(
-                image.getBitmapBounds(), image.getBitmap().getWidth(), image.getWidth(),
-                new Point(loc[0], loc[1]), this, new DragOptions());
-        return true;
-    }
-
-    //
-    // Drag related handling methods that implement {@link DragSource} interface.
-    //
-
-    @Override
-    public boolean supportsAppInfoDropTarget() {
-        return true;
-    }
-
-    /*
-     * Both this method and {@link #supportsFlingToDelete} has to return {@code false} for the
-     * {@link DeleteDropTarget} to be invisible.)
-     */
-    @Override
-    public boolean supportsDeleteDropTarget() {
-        return false;
-    }
-
-    @Override
-    public float getIntrinsicIconScaleFactor() {
-        return 0;
-    }
-
-    @Override
-    public void onDropCompleted(View target, DragObject d, boolean isFlingToDelete,
-            boolean success) {
-        if (LOGD) {
-            Log.d(TAG, "onDropCompleted");
-        }
-        if (isFlingToDelete || !success || (target != mLauncher.getWorkspace() &&
-                !(target instanceof DeleteDropTarget) && !(target instanceof Folder))) {
-            // Exit spring loaded mode if we have not successfully dropped or have not handled the
-            // drop in Workspace
-            mLauncher.exitSpringLoadedDragModeDelayed(true,
-                    Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
-        }
-        mLauncher.unlockScreenOrientation(false);
-
-        if (!success) {
-            d.deferDragViewCleanupPostAnimation = false;
-        }
-    }
-
-    /**
-     * Initialize the widget data model.
-     */
-    public void setWidgets(MultiHashMap<PackageItemInfo, WidgetItem> model) {
-        mAdapter.setWidgets(model);
-
-        View loader = getContentView().findViewById(R.id.loader);
-        if (loader != null) {
-            ((ViewGroup) getContentView()).removeView(loader);
-        }
-    }
-
-    public boolean isEmpty() {
-        return mAdapter.getItemCount() == 0;
-    }
-
-    public List<WidgetItem> getWidgetsForPackageUser(PackageUserKey packageUserKey) {
-        return mAdapter.copyWidgetsForPackageUser(packageUserKey);
-    }
-
-    @Override
-    public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
-        targetParent.containerType = ContainerType.WIDGETS;
-    }
-}
\ No newline at end of file
diff --git a/src/com/android/launcher3/widget/WidgetsDiffReporter.java b/src/com/android/launcher3/widget/WidgetsDiffReporter.java
index 52deec3..d67f403 100644
--- a/src/com/android/launcher3/widget/WidgetsDiffReporter.java
+++ b/src/com/android/launcher3/widget/WidgetsDiffReporter.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3.widget;
 
+import android.support.v7.widget.RecyclerView;
 import android.util.Log;
 
 import com.android.launcher3.IconCache;
@@ -26,26 +27,18 @@
 import java.util.Iterator;
 
 /**
- * Do diff on widget's tray list items and call the {@link NotifyListener} methods accordingly.
+ * Do diff on widget's tray list items and call the {@link RecyclerView.Adapter}
+ * methods accordingly.
  */
 public class WidgetsDiffReporter {
-    private final boolean DEBUG = false;
-    private final String TAG = "WidgetsDiffReporter";
+    private static final boolean DEBUG = false;
+    private static final String TAG = "WidgetsDiffReporter";
+
     private final IconCache mIconCache;
-    private NotifyListener mListener;
+    private final RecyclerView.Adapter mListener;
 
-    public interface NotifyListener {
-        void notifyDataSetChanged();
-        void notifyItemChanged(int index);
-        void notifyItemInserted(int index);
-        void notifyItemRemoved(int index);
-    }
-
-    public WidgetsDiffReporter(IconCache iconCache) {
+    public WidgetsDiffReporter(IconCache iconCache, RecyclerView.Adapter listener) {
         mIconCache = iconCache;
-    }
-
-    public void setListener(NotifyListener listener) {
         mListener = listener;
     }
 
@@ -55,9 +48,17 @@
             Log.d(TAG, "process oldEntries#=" + currentEntries.size()
                     + " newEntries#=" + newEntries.size());
         }
-        if (currentEntries.size() == 0 && newEntries.size() > 0) {
-            currentEntries.addAll(newEntries);
-            mListener.notifyDataSetChanged();
+        // Early exit if either of the list is empty
+        if (currentEntries.isEmpty() || newEntries.isEmpty()) {
+            // Skip if both list are empty.
+            // On rotation, we open the widget tray with empty. Then try to fetch the list again
+            // when the animation completes (which still gives empty). And we get the final result
+            // when the bind actually completes.
+            if (currentEntries.size() != newEntries.size()) {
+                currentEntries.clear();
+                currentEntries.addAll(newEntries);
+                mListener.notifyDataSetChanged();
+            }
             return;
         }
         ArrayList<WidgetListRowEntry> orgEntries =
diff --git a/src/com/android/launcher3/widget/WidgetsFullSheet.java b/src/com/android/launcher3/widget/WidgetsFullSheet.java
new file mode 100644
index 0000000..a40ea1b
--- /dev/null
+++ b/src/com/android/launcher3/widget/WidgetsFullSheet.java
@@ -0,0 +1,227 @@
+/*
+ * 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.widget;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.PropertyValuesHolder;
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.animation.AnimationUtils;
+
+import com.android.launcher3.Insettable;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherAppWidgetHost.ProviderChangedListener;
+import com.android.launcher3.R;
+
+/**
+ * Popup for showing the full list of available widgets
+ */
+public class WidgetsFullSheet extends BaseWidgetSheet
+        implements Insettable, ProviderChangedListener {
+
+    private static final long DEFAULT_OPEN_DURATION = 267;
+    private static final long FADE_IN_DURATION = 150;
+    private static final float VERTICAL_START_POSITION = 0.3f;
+
+    private static final Rect sTempRect = new Rect();
+
+    private final Rect mInsets = new Rect();
+
+    private final WidgetsListAdapter mAdapter;
+
+    private View mNavBarScrim;
+    private WidgetsRecyclerView mRecyclerView;
+
+    public WidgetsFullSheet(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        LauncherAppState apps = LauncherAppState.getInstance(context);
+        mAdapter = new WidgetsListAdapter(context,
+                LayoutInflater.from(context), apps.getWidgetCache(), apps.getIconCache(),
+                this, this);
+    }
+
+    public WidgetsFullSheet(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mContent = findViewById(R.id.container);
+        mNavBarScrim = findViewById(R.id.nav_bar_bg);
+
+        mRecyclerView = findViewById(R.id.widgets_list_view);
+        mRecyclerView.setAdapter(mAdapter);
+        mAdapter.setApplyBitmapDeferred(true, mRecyclerView);
+
+        mGradientView = findViewById(R.id.gradient_bg);
+        mGradientView.setProgress(1, false);
+
+        onWidgetsBound();
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        mLauncher.getAppWidgetHost().addProviderChangeListener(this);
+        notifyWidgetProvidersChanged();
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        mLauncher.getAppWidgetHost().removeProviderChangeListener(this);
+    }
+
+    @Override
+    public void setInsets(Rect insets) {
+        mInsets.set(insets);
+
+        mNavBarScrim.getLayoutParams().height = insets.bottom;
+        mRecyclerView.setPadding(
+                mRecyclerView.getPaddingLeft(), mRecyclerView.getPaddingTop(),
+                mRecyclerView.getPaddingRight(), insets.bottom);
+        if (insets.bottom > 0) {
+            setupNavBarColor();
+        }
+        requestLayout();
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int widthUsed;
+        if (mInsets.bottom > 0) {
+            // If we have bottom insets, we do not show the scrim as it would overlap
+            // with the navbar scrim
+            mGradientView.setVisibility(View.INVISIBLE);
+            widthUsed = 0;
+        } else {
+            mLauncher.getDeviceProfile().getWorkspacePadding(sTempRect);
+            widthUsed = Math.max(sTempRect.left + sTempRect.right,
+                    2 * (mInsets.left + mInsets.right));
+        }
+
+        int heightUsed = mInsets.top + mLauncher.getDeviceProfile().edgeMarginPx;
+        measureChildWithMargins(mContent, widthMeasureSpec,
+                widthUsed, heightMeasureSpec, heightUsed);
+        measureChild(mGradientView, widthMeasureSpec, heightMeasureSpec);
+        setMeasuredDimension(mGradientView.getMeasuredWidth(), mGradientView.getMeasuredHeight());
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        int width = r - l;
+        int height = b - t;
+        mGradientView.layout(0, 0, width, height);
+
+        // Content is laid out as center bottom aligned
+        int contentWidth = mContent.getMeasuredWidth();
+        int contentLeft = (width - contentWidth) / 2;
+        mContent.layout(contentLeft, height - mContent.getMeasuredHeight(),
+                contentLeft + contentWidth, height);
+
+        setTranslationShift(mTranslationShift);
+    }
+
+    @Override
+    public void notifyWidgetProvidersChanged() {
+        mLauncher.refreshAndBindWidgetsForPackageUser(null);
+    }
+
+    @Override
+    protected void onWidgetsBound() {
+        mAdapter.setWidgets(mLauncher.getPopupDataProvider().getAllWidgets());
+    }
+
+    private void open(boolean animate) {
+        if (mIsOpen) {
+            return;
+        }
+        mIsOpen = true;
+        if (animate) {
+            if (mLauncher.getDragLayer().getInsets().bottom > 0) {
+                mContent.setAlpha(0);
+                setTranslationShift(VERTICAL_START_POSITION);
+            }
+            mOpenCloseAnimator.setValues(
+                    PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
+            mOpenCloseAnimator
+                    .setDuration(DEFAULT_OPEN_DURATION)
+                    .setInterpolator(AnimationUtils.loadInterpolator(
+                            getContext(), android.R.interpolator.linear_out_slow_in));
+            mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mRecyclerView.setLayoutFrozen(false);
+                    mAdapter.setApplyBitmapDeferred(false, mRecyclerView);
+                    mOpenCloseAnimator.removeListener(this);
+                }
+            });
+            post(new Runnable() {
+                @Override
+                public void run() {
+                    mRecyclerView.setLayoutFrozen(true);
+                    mOpenCloseAnimator.start();
+                    mContent.animate().alpha(1).setDuration(FADE_IN_DURATION);
+                }
+            });
+        } else {
+            setTranslationShift(TRANSLATION_SHIFT_OPENED);
+            mAdapter.setApplyBitmapDeferred(false, mRecyclerView);
+        }
+    }
+
+    @Override
+    protected void handleClose(boolean animate) {
+        handleClose(animate, DEFAULT_OPEN_DURATION);
+    }
+
+    @Override
+    protected boolean isOfType(int type) {
+        return (type & TYPE_WIDGETS_FULL_SHEET) != 0;
+    }
+
+    @Override
+    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+        // Disable swipe down when recycler view is scrolling
+        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+            mNoIntercept = false;
+            if (mLauncher.getDragLayer().isEventOverView(mContent, ev)) {
+                mNoIntercept = !mRecyclerView.shouldContainerScroll(ev, mLauncher.getDragLayer());
+            }
+        }
+        return super.onControllerInterceptTouchEvent(ev);
+    }
+
+    public static WidgetsFullSheet show(Launcher launcher, boolean animate) {
+        WidgetsFullSheet sheet = (WidgetsFullSheet) launcher.getLayoutInflater()
+                .inflate(R.layout.widgets_full_sheet, launcher.getDragLayer(), false);
+        launcher.getDragLayer().addView(sheet);
+        sheet.open(animate);
+        return sheet;
+    }
+
+    @Override
+    protected int getElementsRowCount() {
+        return mAdapter.getItemCount();
+    }
+}
diff --git a/src/com/android/launcher3/widget/WidgetsListAdapter.java b/src/com/android/launcher3/widget/WidgetsListAdapter.java
index 6b1800c..0147ea4 100644
--- a/src/com/android/launcher3/widget/WidgetsListAdapter.java
+++ b/src/com/android/launcher3/widget/WidgetsListAdapter.java
@@ -25,13 +25,11 @@
 import android.view.View.OnLongClickListener;
 import android.view.ViewGroup;
 
+import com.android.launcher3.IconCache;
 import com.android.launcher3.R;
 import com.android.launcher3.WidgetPreviewLoader;
-import com.android.launcher3.compat.AlphabeticIndexCompat;
-import com.android.launcher3.model.PackageItemInfo;
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.util.LabelComparator;
-import com.android.launcher3.util.MultiHashMap;
 import com.android.launcher3.util.PackageUserKey;
 
 import java.util.ArrayList;
@@ -39,7 +37,6 @@
 import java.util.Comparator;
 import java.util.Iterator;
 import java.util.List;
-import java.util.Map;
 
 /**
  * List view adapter for the widget tray.
@@ -56,7 +53,6 @@
 
     private final WidgetPreviewLoader mWidgetPreviewLoader;
     private final LayoutInflater mLayoutInflater;
-    private final AlphabeticIndexCompat mIndexer;
 
     private final OnClickListener mIconClickListener;
     private final OnLongClickListener mIconLongClickListener;
@@ -64,56 +60,43 @@
     private ArrayList<WidgetListRowEntry> mEntries = new ArrayList<>();
     private final WidgetsDiffReporter mDiffReporter;
 
+    private boolean mApplyBitmapDeferred;
+
     public WidgetsListAdapter(Context context, LayoutInflater layoutInflater,
-            WidgetPreviewLoader widgetPreviewLoader, AlphabeticIndexCompat indexCompat,
-            OnClickListener iconClickListener, OnLongClickListener iconLongClickListener,
-            WidgetsDiffReporter diffReporter) {
+            WidgetPreviewLoader widgetPreviewLoader, IconCache iconCache,
+            OnClickListener iconClickListener, OnLongClickListener iconLongClickListener) {
         mLayoutInflater = layoutInflater;
         mWidgetPreviewLoader = widgetPreviewLoader;
-        mIndexer = indexCompat;
         mIconClickListener = iconClickListener;
         mIconLongClickListener = iconLongClickListener;
         mIndent = context.getResources().getDimensionPixelSize(R.dimen.widget_section_indent);
-        mDiffReporter = diffReporter;
+        mDiffReporter = new WidgetsDiffReporter(iconCache, this);
     }
 
-    public void setNotifyListener() {
-        mDiffReporter.setListener(new WidgetsDiffReporter.NotifyListener() {
-            @Override
-            public void notifyDataSetChanged() {
-                WidgetsListAdapter.this.notifyDataSetChanged();
-            }
+    /**
+     * Defers applying bitmap on all the {@link WidgetCell} in the {@param rv}
+     *
+     * @see WidgetCell#setApplyBitmapDeferred(boolean)
+     */
+    public void setApplyBitmapDeferred(boolean isDeferred, RecyclerView rv) {
+        mApplyBitmapDeferred = isDeferred;
 
-            @Override
-            public void notifyItemChanged(int index) {
-                WidgetsListAdapter.this.notifyItemChanged(index);
+        for (int i = rv.getChildCount() - 1; i >= 0; i--) {
+            WidgetsRowViewHolder holder = (WidgetsRowViewHolder)
+                    rv.getChildViewHolder(rv.getChildAt(i));
+            for (int j = holder.cellContainer.getChildCount() - 1; j >= 0; j--) {
+                View v = holder.cellContainer.getChildAt(j);
+                if (v instanceof WidgetCell) {
+                    ((WidgetCell) v).setApplyBitmapDeferred(mApplyBitmapDeferred);
+                }
             }
-
-            @Override
-            public void notifyItemInserted(int index) {
-                WidgetsListAdapter.this.notifyItemInserted(index);
-            }
-
-            @Override
-            public void notifyItemRemoved(int index) {
-                WidgetsListAdapter.this.notifyItemRemoved(index);
-            }
-        });
+        }
     }
 
     /**
      * Update the widget list.
      */
-    public void setWidgets(MultiHashMap<PackageItemInfo, WidgetItem> widgets) {
-        ArrayList<WidgetListRowEntry> tempEntries = new ArrayList<>();
-
-        WidgetItemComparator widgetComparator = new WidgetItemComparator();
-        for (Map.Entry<PackageItemInfo, ArrayList<WidgetItem>> entry : widgets.entrySet()) {
-            WidgetListRowEntry row = new WidgetListRowEntry(entry.getKey(), entry.getValue());
-            row.titleSectionName = mIndexer.computeSectionName(row.pkgItem.title);
-            Collections.sort(row.widgets, widgetComparator);
-            tempEntries.add(row);
-        }
+    public void setWidgets(ArrayList<WidgetListRowEntry> tempEntries) {
         WidgetListRowEntryComparator rowComparator = new WidgetListRowEntryComparator();
         Collections.sort(tempEntries, rowComparator);
         mDiffReporter.process(mEntries, tempEntries, rowComparator);
@@ -128,26 +111,6 @@
         return mEntries.get(pos).titleSectionName;
     }
 
-    /**
-     * Copies and returns the widgets associated with the package and user of the ComponentKey.
-     */
-    public List<WidgetItem> copyWidgetsForPackageUser(PackageUserKey packageUserKey) {
-        for (WidgetListRowEntry entry : mEntries) {
-            if (entry.pkgItem.packageName.equals(packageUserKey.mPackageName)) {
-                ArrayList<WidgetItem> widgets = new ArrayList<>(entry.widgets);
-                // Remove widgets not associated with the correct user.
-                Iterator<WidgetItem> iterator = widgets.iterator();
-                while (iterator.hasNext()) {
-                    if (!iterator.next().user.equals(packageUserKey.mUser)) {
-                        iterator.remove();
-                    }
-                }
-                return widgets.isEmpty() ? null : widgets;
-            }
-        }
-        return null;
-    }
-
     @Override
     public void onBindViewHolder(WidgetsRowViewHolder holder, int pos) {
         WidgetListRowEntry entry = mEntries.get(pos);
@@ -194,6 +157,7 @@
         for (int i=0; i < infoList.size(); i++) {
             WidgetCell widget = (WidgetCell) row.getChildAt(2*i);
             widget.applyFromCellItem(infoList.get(i), mWidgetPreviewLoader);
+            widget.setApplyBitmapDeferred(mApplyBitmapDeferred);
             widget.ensurePreview();
             widget.setVisibility(View.VISIBLE);
 
@@ -253,5 +217,4 @@
             return mComparator.compare(a.pkgItem.title.toString(), b.pkgItem.title.toString());
         }
     }
-
 }
diff --git a/src/com/android/launcher3/widget/WidgetsRecyclerView.java b/src/com/android/launcher3/widget/WidgetsRecyclerView.java
index 9730a82..89c88a4 100644
--- a/src/com/android/launcher3/widget/WidgetsRecyclerView.java
+++ b/src/com/android/launcher3/widget/WidgetsRecyclerView.java
@@ -17,12 +17,12 @@
 package com.android.launcher3.widget;
 
 import android.content.Context;
-import android.graphics.Color;
 import android.support.v7.widget.LinearLayoutManager;
 import android.util.AttributeSet;
 import android.view.View;
 
 import com.android.launcher3.BaseRecyclerView;
+import com.android.launcher3.R;
 
 /**
  * The widgets recycler view.
@@ -32,6 +32,8 @@
     private static final String TAG = "WidgetsRecyclerView";
     private WidgetsListAdapter mAdapter;
 
+    private final int mScrollbarTop;
+
     public WidgetsRecyclerView(Context context) {
         this(context, null);
     }
@@ -43,6 +45,7 @@
     public WidgetsRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) {
         // API 21 and below only support 3 parameter ctor.
         super(context, attrs, defStyleAttr);
+        mScrollbarTop = getResources().getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
     }
 
     public WidgetsRecyclerView(Context context, AttributeSet attrs, int defStyleAttr,
@@ -130,13 +133,16 @@
     @Override
     protected int getAvailableScrollHeight() {
         View child = getChildAt(0);
-        int height = child.getMeasuredHeight() * mAdapter.getItemCount();
-        int totalHeight = getPaddingTop() + height + getPaddingBottom();
-        int availableScrollHeight = totalHeight - getScrollbarTrackHeight();
-        return availableScrollHeight;
+        return child.getMeasuredHeight() * mAdapter.getItemCount() - getScrollbarTrackHeight()
+                - mScrollbarTop;
     }
 
     private boolean isModelNotReady() {
         return mAdapter.getItemCount() == 0;
     }
+
+    @Override
+    public int getScrollBarTop() {
+        return mScrollbarTop;
+    }
 }
\ No newline at end of file
diff --git a/src/com/android/launcher3/widget/custom/CustomAppWidgetProviderInfo.java b/src/com/android/launcher3/widget/custom/CustomAppWidgetProviderInfo.java
new file mode 100644
index 0000000..1086987
--- /dev/null
+++ b/src/com/android/launcher3/widget/custom/CustomAppWidgetProviderInfo.java
@@ -0,0 +1,103 @@
+/*
+ * 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.widget.custom;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.Utilities;
+
+/**
+ * Custom app widget provider info that can be used as a widget, but provide extra functionality
+ * by allowing custom code and views.
+ */
+public class CustomAppWidgetProviderInfo extends LauncherAppWidgetProviderInfo
+        implements Parcelable {
+
+    public final int providerId;
+
+    protected CustomAppWidgetProviderInfo(Parcel parcel, boolean readSelf, int providerId) {
+        super(parcel);
+        if (readSelf) {
+            this.providerId = parcel.readInt();
+
+            provider = new ComponentName(parcel.readString(), CLS_CUSTOM_WIDGET_PREFIX + providerId);
+
+            label = parcel.readString();
+            initialLayout = parcel.readInt();
+            icon = parcel.readInt();
+            previewImage = parcel.readInt();
+
+            resizeMode = parcel.readInt();
+            spanX = parcel.readInt();
+            spanY = parcel.readInt();
+            minSpanX = parcel.readInt();
+            minSpanY = parcel.readInt();
+        } else {
+            this.providerId = providerId;
+        }
+    }
+
+    @Override
+    public void initSpans(Context context) { }
+
+    @Override
+    public String getLabel(PackageManager packageManager) {
+        return Utilities.trim(label);
+    }
+
+    @Override
+    public String toString() {
+        return "WidgetProviderInfo(" + provider + ")";
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        super.writeToParcel(out, flags);
+        out.writeInt(providerId);
+        out.writeString(provider.getPackageName());
+
+        out.writeString(label);
+        out.writeInt(initialLayout);
+        out.writeInt(icon);
+        out.writeInt(previewImage);
+
+        out.writeInt(resizeMode);
+        out.writeInt(spanX);
+        out.writeInt(spanY);
+        out.writeInt(minSpanX);
+        out.writeInt(minSpanY);
+    }
+
+    public static final Parcelable.Creator<CustomAppWidgetProviderInfo> CREATOR
+            = new Parcelable.Creator<CustomAppWidgetProviderInfo>() {
+
+        @Override
+        public CustomAppWidgetProviderInfo createFromParcel(Parcel parcel) {
+            return new CustomAppWidgetProviderInfo(parcel, true, 0);
+        }
+
+        @Override
+        public CustomAppWidgetProviderInfo[] newArray(int size) {
+            return new CustomAppWidgetProviderInfo[size];
+        }
+    };
+}
diff --git a/src/com/android/launcher3/widget/custom/CustomWidgetParser.java b/src/com/android/launcher3/widget/custom/CustomWidgetParser.java
new file mode 100644
index 0000000..00720c4
--- /dev/null
+++ b/src/com/android/launcher3/widget/custom/CustomWidgetParser.java
@@ -0,0 +1,142 @@
+/*
+ * 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.widget.custom;
+
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.os.Parcel;
+import android.os.Process;
+import android.util.SparseArray;
+import android.util.Xml;
+
+import com.android.launcher3.LauncherAppWidgetInfo;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.android.launcher3.LauncherAppWidgetProviderInfo.CLS_CUSTOM_WIDGET_PREFIX;
+
+/**
+ * Utility class to parse {@ink CustomAppWidgetProviderInfo} definitions from xml
+ */
+public class CustomWidgetParser {
+
+    private static List<LauncherAppWidgetProviderInfo> sCustomWidgets;
+    private static SparseArray<ComponentName> sWidgetsIdMap;
+
+    public static List<LauncherAppWidgetProviderInfo> getCustomWidgets(Context context) {
+        if (sCustomWidgets == null) {
+            // Synchronization not needed as it it safe to load multiple times
+            parseCustomWidgets(context);
+        }
+
+        return sCustomWidgets;
+    }
+
+    public static int getWidgetIdForCustomProvider(Context context, ComponentName provider) {
+        if (sWidgetsIdMap == null) {
+            parseCustomWidgets(context);
+        }
+        int index = sWidgetsIdMap.indexOfValue(provider);
+        if (index >= 0) {
+            return LauncherAppWidgetInfo.CUSTOM_WIDGET_ID - sWidgetsIdMap.keyAt(index);
+        } else {
+            return AppWidgetManager.INVALID_APPWIDGET_ID;
+        }
+    }
+
+    public static LauncherAppWidgetProviderInfo getWidgetProvider(Context context, int widgetId) {
+        if (sWidgetsIdMap == null || sCustomWidgets == null) {
+            parseCustomWidgets(context);
+        }
+        ComponentName cn = sWidgetsIdMap.get(LauncherAppWidgetInfo.CUSTOM_WIDGET_ID - widgetId);
+        for (LauncherAppWidgetProviderInfo info : sCustomWidgets) {
+            if (info.provider.equals(cn)) {
+                return info;
+            }
+        }
+        return null;
+    }
+
+    private static void parseCustomWidgets(Context context) {
+        ArrayList<LauncherAppWidgetProviderInfo> widgets = new ArrayList<>();
+        SparseArray<ComponentName> idMap = new SparseArray<>();
+
+        List<AppWidgetProviderInfo> providers = AppWidgetManager.getInstance(context)
+                .getInstalledProvidersForProfile(Process.myUserHandle());
+        if (providers.isEmpty()) {
+            sCustomWidgets = widgets;
+            sWidgetsIdMap = idMap;
+            return;
+        }
+
+        Parcel parcel = Parcel.obtain();
+        providers.get(0).writeToParcel(parcel, 0);
+
+        try (XmlResourceParser parser = context.getResources().getXml(R.xml.custom_widgets)) {
+            final int depth = parser.getDepth();
+            int type;
+
+            while (((type = parser.next()) != XmlPullParser.END_TAG ||
+                    parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
+                if ((type == XmlPullParser.START_TAG) && "widget".equals(parser.getName())) {
+                    TypedArray a = context.obtainStyledAttributes(
+                            Xml.asAttributeSet(parser), R.styleable.CustomAppWidgetProviderInfo);
+
+                    parcel.setDataPosition(0);
+                    CustomAppWidgetProviderInfo info = newInfo(a, parcel, context);
+                    widgets.add(info);
+                    a.recycle();
+
+                    idMap.put(info.providerId, info.provider);
+                }
+            }
+        } catch (IOException | XmlPullParserException e) {
+            throw new RuntimeException(e);
+        }
+        parcel.recycle();
+        sCustomWidgets = widgets;
+        sWidgetsIdMap = idMap;
+    }
+
+    private static CustomAppWidgetProviderInfo newInfo(TypedArray a, Parcel parcel, Context context) {
+        int providerId = a.getInt(R.styleable.CustomAppWidgetProviderInfo_providerId, 0);
+        CustomAppWidgetProviderInfo info = new CustomAppWidgetProviderInfo(parcel, false, providerId);
+        info.provider = new ComponentName(context.getPackageName(), CLS_CUSTOM_WIDGET_PREFIX + providerId);
+
+        info.label = a.getString(R.styleable.CustomAppWidgetProviderInfo_android_label);
+        info.initialLayout = a.getResourceId(R.styleable.CustomAppWidgetProviderInfo_android_initialLayout, 0);
+        info.icon = a.getResourceId(R.styleable.CustomAppWidgetProviderInfo_android_icon, 0);
+        info.previewImage = a.getResourceId(R.styleable.CustomAppWidgetProviderInfo_android_previewImage, 0);
+        info.resizeMode = a.getInt(R.styleable.CustomAppWidgetProviderInfo_android_resizeMode, 0);
+
+        info.spanX = a.getInt(R.styleable.CustomAppWidgetProviderInfo_numColumns, 1);
+        info.spanY = a.getInt(R.styleable.CustomAppWidgetProviderInfo_numRows, 1);
+        info.minSpanX = a.getInt(R.styleable.CustomAppWidgetProviderInfo_numMinColumns, 1);
+        info.minSpanY = a.getInt(R.styleable.CustomAppWidgetProviderInfo_numMinRows, 1);
+        return info;
+    }
+}
diff --git a/src_config/com/android/launcher3/BuildConfig.java b/src_config/com/android/launcher3/BuildConfig.java
deleted file mode 100644
index 4df75a1..0000000
--- a/src_config/com/android/launcher3/BuildConfig.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * 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;
-
-/**
- * Config file used by Make. This file is automatically generated when using gradle.
- */
-public class BuildConfig {
-    public static final String APPLICATION_ID = "com.android.launcher3";
-}
diff --git a/src/com/android/launcher3/accessibility/OverviewAccessibilityDelegate.java b/src_ui_overrides/com/android/launcher3/uioverrides/OverviewAccessibilityDelegate.java
similarity index 87%
rename from src/com/android/launcher3/accessibility/OverviewAccessibilityDelegate.java
rename to src_ui_overrides/com/android/launcher3/uioverrides/OverviewAccessibilityDelegate.java
index 29dd95c..88a1e10 100644
--- a/src/com/android/launcher3/accessibility/OverviewAccessibilityDelegate.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/OverviewAccessibilityDelegate.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.launcher3.accessibility;
+package com.android.launcher3.uioverrides;
 
 import android.content.Context;
 import android.os.Bundle;
@@ -24,6 +24,7 @@
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
 
 import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 
@@ -54,17 +55,18 @@
     @Override
     public boolean performAccessibilityAction(View host, int action, Bundle args) {
         Launcher launcher = Launcher.getLauncher(host.getContext());
+        OverviewPanel overviewPanel = launcher.findViewById(R.id.overview_panel);
         if (action == OVERVIEW) {
-            launcher.showOverviewMode(true);
+            launcher.getStateManager().goToState(LauncherState.OVERVIEW);
             return true;
         } else if (action == WALLPAPERS) {
             launcher.onClickWallpaperPicker(host);
             return true;
         } else if (action == WIDGETS) {
-            launcher.onClickAddWidgetButton(host);
+            overviewPanel.onClickAddWidgetButton();
             return true;
         } else if (action == SETTINGS) {
-            launcher.onClickSettingsButton(host);
+            overviewPanel.onClickSettingsButton(host);
             return true;
         }
         return super.performAccessibilityAction(host, action, args);
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/OverviewPanel.java b/src_ui_overrides/com/android/launcher3/uioverrides/OverviewPanel.java
new file mode 100644
index 0000000..3ce1014
--- /dev/null
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/OverviewPanel.java
@@ -0,0 +1,198 @@
+/*
+ * 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.uioverrides;
+
+import static com.android.launcher3.WorkspaceStateTransitionAnimation.NO_ANIM_PROPERTY_SETTER;
+
+import android.animation.AnimatorSet;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.Toast;
+
+import com.android.launcher3.Insettable;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager;
+import com.android.launcher3.LauncherStateManager.AnimationConfig;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.WorkspaceStateTransitionAnimation.AnimatedPropertySetter;
+import com.android.launcher3.WorkspaceStateTransitionAnimation.PropertySetter;
+import com.android.launcher3.anim.AnimationLayerSet;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
+import com.android.launcher3.widget.WidgetsFullSheet;
+
+public class OverviewPanel extends LinearLayout implements Insettable, View.OnClickListener,
+        View.OnLongClickListener, LauncherStateManager.StateHandler {
+
+    // Out of 100, the percent of space the overview bar should try and take vertically.
+    private static final float OVERVIEW_ICON_ZONE_RATIO = 0.22f;
+
+    private final Launcher mLauncher;
+
+    public OverviewPanel(Context context) {
+        this(context, null);
+    }
+
+    public OverviewPanel(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public OverviewPanel(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        mLauncher = Launcher.getLauncher(context);
+        setAlpha(0);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        int visibleChildCount = 3;
+        // Attach buttons.
+        attachListeners(findViewById(R.id.wallpaper_button));
+        attachListeners(findViewById(R.id.widget_button));
+
+        View settingsButton = findViewById(R.id.settings_button);
+        if (mLauncher.hasSettings()) {
+            attachListeners(settingsButton);
+        } else {
+            settingsButton.setVisibility(GONE);
+            visibleChildCount--;
+        }
+
+        // Init UI
+        Resources res = getResources();
+        int itemWidthPx =
+                res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_item_width);
+        int spacerWidthPx =
+                res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_spacer_width);
+
+        int totalItemWidth = visibleChildCount * itemWidthPx;
+        int maxWidth = totalItemWidth + (visibleChildCount - 1) * spacerWidthPx;
+
+        getLayoutParams().width = Math.min(mLauncher.getDeviceProfile().availableWidthPx, maxWidth);
+        getLayoutParams().height = getButtonBarHeight(mLauncher);
+    }
+
+    private void attachListeners(View view) {
+        view.setOnClickListener(this);
+        view.setOnLongClickListener(this);
+    }
+
+    @Override
+    public void setInsets(Rect insets) {
+        ((FrameLayout.LayoutParams) getLayoutParams()).bottomMargin = insets.bottom;
+    }
+
+    @Override
+    public void onClick(View view) {
+        handleViewClick(view, Action.Touch.TAP);
+    }
+
+    @Override
+    public boolean onLongClick(View view) {
+        return handleViewClick(view, Action.Touch.LONGPRESS);
+    }
+
+    private boolean handleViewClick(View view, int action) {
+        if (mLauncher.getWorkspace().isSwitchingState()) {
+            return false;
+        }
+
+        final int controlType;
+        if (view.getId() == R.id.wallpaper_button) {
+            mLauncher.onClickWallpaperPicker(view);
+            controlType = ControlType.WALLPAPER_BUTTON;
+        } else if (view.getId() == R.id.widget_button) {
+            onClickAddWidgetButton();
+            controlType = ControlType.WIDGETS_BUTTON;
+        } else if (view.getId() == R.id.settings_button) {
+            onClickSettingsButton(view);
+            controlType = ControlType.SETTINGS_BUTTON;
+        } else {
+            return false;
+        }
+
+        mLauncher.getUserEventDispatcher().logActionOnControl(action, controlType);
+        return true;
+    }
+
+    /**
+     * Event handler for the (Add) Widgets button that appears after a long press
+     * on the home screen.
+     */
+    public void onClickAddWidgetButton() {
+        if (getContext().getPackageManager().isSafeMode()) {
+            Toast.makeText(mLauncher, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
+        } else {
+            WidgetsFullSheet.show(mLauncher, true /* animated */);
+        }
+    }
+
+    /**
+     * Event handler for a click on the settings button that appears after a long press
+     * on the home screen.
+     */
+    public void onClickSettingsButton(View v) {
+        Intent intent = new Intent(Intent.ACTION_APPLICATION_PREFERENCES)
+                .setPackage(getContext().getPackageName());
+        intent.setSourceBounds(mLauncher.getViewBounds(v));
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        getContext().startActivity(intent, mLauncher.getActivityLaunchOptions(v));
+    }
+
+    @Override
+    public void setState(LauncherState state) {
+        setState(state, NO_ANIM_PROPERTY_SETTER);
+    }
+
+    @Override
+    public void setStateWithAnimation(LauncherState toState, AnimationLayerSet layerViews,
+            AnimatorSet anim, AnimationConfig config) {
+        setState(toState, new AnimatedPropertySetter(config.duration, layerViews, anim));
+    }
+
+    private void setState(LauncherState state, PropertySetter setter) {
+        boolean isOverview = state == LauncherState.OVERVIEW;
+        float finalHotseatAlpha = isOverview ? 0 : 1;
+
+        setter.setViewAlpha(null, this, isOverview ? 1 : 0);
+        setter.setViewAlpha(
+                mLauncher.getWorkspace().createHotseatAlphaAnimator(finalHotseatAlpha),
+                mLauncher.getHotseat(), finalHotseatAlpha);
+    }
+
+    public static int getButtonBarHeight(Launcher launcher) {
+        int zoneHeight = (int) (OVERVIEW_ICON_ZONE_RATIO *
+                launcher.getDeviceProfile().availableHeightPx);
+        Resources res = launcher.getResources();
+        int overviewModeMinIconZoneHeightPx =
+                res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_min_icon_zone_height);
+        int overviewModeMaxIconZoneHeightPx =
+                res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_max_icon_zone_height);
+        return Utilities.boundToRange(zoneHeight,
+                overviewModeMinIconZoneHeightPx,
+                overviewModeMaxIconZoneHeightPx);
+    }
+}
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/OverviewState.java b/src_ui_overrides/com/android/launcher3/uioverrides/OverviewState.java
new file mode 100644
index 0000000..dcf7453
--- /dev/null
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/OverviewState.java
@@ -0,0 +1,83 @@
+/*
+ * 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.uioverrides;
+
+import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
+import static com.android.launcher3.Utilities.isAccessibilityEnabled;
+
+import android.graphics.Rect;
+import android.view.View;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.Workspace;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+
+/**
+ * Definition for overview state
+ */
+public class OverviewState extends LauncherState {
+
+    // The percent to shrink the workspace during overview mode
+    private static final float SCALE_FACTOR = 0.7f;
+
+    private static final int STATE_FLAGS = FLAG_SHOW_SCRIM | FLAG_MULTI_PAGE;
+
+    public OverviewState(int id) {
+        super(id, ContainerType.WORKSPACE, OVERVIEW_TRANSITION_MS, 1f, STATE_FLAGS);
+    }
+
+    @Override
+    public float[] getWorkspaceScaleAndTranslation(Launcher launcher) {
+        DeviceProfile grid = launcher.getDeviceProfile();
+        Workspace ws = launcher.getWorkspace();
+        Rect insets = launcher.getDragLayer().getInsets();
+
+        int overviewButtonBarHeight = OverviewPanel.getButtonBarHeight(launcher);
+        int scaledHeight = (int) (SCALE_FACTOR * ws.getNormalChildHeight());
+        Rect workspacePadding = grid.getWorkspacePadding(null);
+        int workspaceTop = insets.top + workspacePadding.top;
+        int workspaceBottom = ws.getViewportHeight() - insets.bottom - workspacePadding.bottom;
+        int overviewTop = insets.top;
+        int overviewBottom = ws.getViewportHeight() - insets.bottom - overviewButtonBarHeight;
+        int workspaceOffsetTopEdge =
+                workspaceTop + ((workspaceBottom - workspaceTop) - scaledHeight) / 2;
+        int overviewOffsetTopEdge = overviewTop + (overviewBottom - overviewTop - scaledHeight) / 2;
+        return new float[] {SCALE_FACTOR, -workspaceOffsetTopEdge + overviewOffsetTopEdge };
+    }
+
+    @Override
+    public void onStateEnabled(Launcher launcher) {
+        launcher.getWorkspace().setPageRearrangeEnabled(true);
+
+        if (isAccessibilityEnabled(launcher)) {
+            launcher.getOverviewPanel().getChildAt(0).performAccessibilityAction(
+                    AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
+        }
+    }
+
+    @Override
+    public void onStateDisabled(Launcher launcher) {
+        launcher.getWorkspace().setPageRearrangeEnabled(false);
+    }
+
+    @Override
+    public View getFinalFocus(Launcher launcher) {
+        return launcher.getOverviewPanel();
+    }
+}
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/PinchToOverviewListener.java b/src_ui_overrides/com/android/launcher3/uioverrides/PinchToOverviewListener.java
new file mode 100644
index 0000000..40bf057
--- /dev/null
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/PinchToOverviewListener.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2016 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.uioverrides;
+
+import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.Utilities.isAccessibilityEnabled;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
+import android.view.ScaleGestureDetector.OnScaleGestureListener;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.Workspace;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.util.TouchController;
+
+/**
+ * Detects pinches and animates the Workspace to/from overview mode.
+ */
+public class PinchToOverviewListener extends AnimatorListenerAdapter
+        implements TouchController, OnScaleGestureListener {
+
+    private static final float ACCEPT_THRESHOLD = 0.65f;
+    /**
+     * The velocity threshold at which a pinch will be completed instead of canceled,
+     * even if the first threshold has not been passed. Measured in scale / millisecond
+     */
+    private static final float FLING_VELOCITY = 0.001f;
+
+    private final ScaleGestureDetector mPinchDetector;
+    private Launcher mLauncher;
+    private Workspace mWorkspace = null;
+    private boolean mPinchStarted = false;
+
+    private AnimatorPlaybackController mCurrentAnimation;
+    private float mCurrentScale;
+    private boolean mShouldGoToFinalState;
+
+    private LauncherState mToState;
+
+    public PinchToOverviewListener(Launcher launcher) {
+        mLauncher = launcher;
+        mPinchDetector = new ScaleGestureDetector(mLauncher, this);
+    }
+
+    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+        mPinchDetector.onTouchEvent(ev);
+        return mPinchStarted;
+    }
+
+    public boolean onControllerTouchEvent(MotionEvent ev) {
+        return mPinchDetector.onTouchEvent(ev);
+    }
+
+    @Override
+    public boolean onScaleBegin(ScaleGestureDetector detector) {
+        if (isAccessibilityEnabled(mLauncher)) {
+            return false;
+        }
+        if (!mLauncher.isInState(NORMAL) && !mLauncher.isInState(OVERVIEW)) {
+            // Don't listen for the pinch gesture if on all apps, widget picker, -1, etc.
+            return false;
+        }
+        if (mCurrentAnimation != null) {
+            // Don't listen for the pinch gesture if we are already animating from a previous one.
+            return false;
+        }
+        if (mLauncher.isWorkspaceLocked()) {
+            // Don't listen for the pinch gesture if the workspace isn't ready.
+            return false;
+        }
+        if (mWorkspace == null) {
+            mWorkspace = mLauncher.getWorkspace();
+        }
+        if (mWorkspace.isSwitchingState()) {
+            // Don't listen for the pinch gesture while switching state, as it will cause a jump
+            // once the state switching animation is complete.
+            return false;
+        }
+        if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
+            // Don't listen for the pinch gesture if a floating view is open.
+            return false;
+        }
+
+        if (mLauncher.getDragController().isDragging()) {
+            mLauncher.getDragController().cancelDrag();
+        }
+
+        mToState = mLauncher.isInState(OVERVIEW) ? NORMAL : OVERVIEW;
+        mCurrentAnimation = mLauncher.getStateManager()
+                .createAnimationToNewWorkspace(mToState, OVERVIEW_TRANSITION_MS);
+        mCurrentAnimation.getTarget().addListener(this);
+        mPinchStarted = true;
+        mCurrentScale = 1;
+        mShouldGoToFinalState = false;
+
+        mCurrentAnimation.dispatchOnStart();
+        return true;
+    }
+
+    @Override
+    public void onAnimationEnd(Animator animation) {
+        mCurrentAnimation = null;
+        mPinchStarted = false;
+    }
+
+    @Override
+    public void onScaleEnd(ScaleGestureDetector detector) {
+        if (mShouldGoToFinalState) {
+            mCurrentAnimation.start();
+        } else {
+            mCurrentAnimation.setEndAction(new Runnable() {
+                @Override
+                public void run() {
+                    mLauncher.getStateManager().goToState(
+                            mToState == OVERVIEW ? NORMAL : OVERVIEW, false);
+                }
+            });
+            mCurrentAnimation.reverse();
+        }
+    }
+
+    @Override
+    public boolean onScale(ScaleGestureDetector detector) {
+        mCurrentScale = detector.getScaleFactor() * mCurrentScale;
+
+        // If we are zooming out, inverse the mCurrentScale so that animationFraction = [0, 1]
+        // 0 => Animation complete
+        // 1=> Animation started
+        float animationFraction = mToState == OVERVIEW ? mCurrentScale : (1 / mCurrentScale);
+
+        float velocity = (1 - detector.getScaleFactor()) / detector.getTimeDelta();
+        if (Math.abs(velocity) >= FLING_VELOCITY) {
+            LauncherState toState = velocity > 0 ? OVERVIEW : NORMAL;
+            mShouldGoToFinalState = toState == mToState;
+        } else {
+            mShouldGoToFinalState = animationFraction <= ACCEPT_THRESHOLD;
+        }
+
+        // Move the transition animation to that duration.
+        mCurrentAnimation.setPlayFraction(1 - animationFraction);
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
new file mode 100644
index 0000000..51cf661
--- /dev/null
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
@@ -0,0 +1,48 @@
+/*
+ * 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.uioverrides;
+
+import static com.android.launcher3.LauncherState.OVERVIEW;
+
+import android.view.View.AccessibilityDelegate;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherStateManager.StateHandler;
+import com.android.launcher3.VerticalSwipeController;
+import com.android.launcher3.util.TouchController;
+
+public class UiFactory {
+
+    public static TouchController[] createTouchControllers(Launcher launcher) {
+        return new TouchController[] {
+                new VerticalSwipeController(launcher), new PinchToOverviewListener(launcher)};
+    }
+
+    public static AccessibilityDelegate newPageIndicatorAccessibilityDelegate() {
+        return new OverviewAccessibilityDelegate();
+    }
+
+    public static StateHandler[] getStateHandler(Launcher launcher) {
+        return new StateHandler[] {
+                (OverviewPanel) launcher.getOverviewPanel(),
+                launcher.getAllAppsController(), launcher.getWorkspace() };
+    }
+
+    public static void onWorkspaceLongPress(Launcher launcher) {
+        launcher.getStateManager().goToState(OVERVIEW);
+    }
+}
diff --git a/tests/src/com/android/launcher3/logging/FileLogTest.java b/tests/src/com/android/launcher3/logging/FileLogTest.java
index 7048c28..9c7cb8f 100644
--- a/tests/src/com/android/launcher3/logging/FileLogTest.java
+++ b/tests/src/com/android/launcher3/logging/FileLogTest.java
@@ -1,41 +1,51 @@
 package com.android.launcher3.logging;
 
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.io.File;
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.Calendar;
 
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
 /**
  * Tests for {@link FileLog}
  */
 @SmallTest
-public class FileLogTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class FileLogTest {
 
     private File mTempDir;
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
+    @Before
+    public void setUp() throws Exception {
         int count = 0;
         do {
-            mTempDir = new File(getContext().getCacheDir(), "log-test-" + (count++));
+            mTempDir = new File(InstrumentationRegistry.getTargetContext().getCacheDir(),
+                    "log-test-" + (count++));
         } while(!mTempDir.mkdir());
 
         FileLog.setDir(mTempDir);
     }
 
-    @Override
-    protected void tearDown() throws Exception {
+    @After
+    public void tearDown() throws Exception {
         // Clear existing logs
         new File(mTempDir, "log-0").delete();
         new File(mTempDir, "log-1").delete();
         mTempDir.delete();
-        super.tearDown();
     }
 
+    @Test
     public void testPrintLog() throws Exception {
         if (!FileLog.ENABLED) {
             return;
@@ -56,6 +66,7 @@
         assertTrue(writer.toString().contains("hoolalala"));
     }
 
+    @Test
     public void testOldFileTruncated() throws Exception {
         if (!FileLog.ENABLED) {
             return;
diff --git a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java
index 82f34e4..401711d 100644
--- a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java
+++ b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java
@@ -7,6 +7,7 @@
 import android.content.Intent;
 import android.graphics.Rect;
 import android.net.Uri;
+import android.support.test.runner.AndroidJUnit4;
 import android.util.Pair;
 
 import com.android.launcher3.ItemInfo;
@@ -15,21 +16,25 @@
 import com.android.launcher3.ShortcutInfo;
 import com.android.launcher3.util.GridOccupancy;
 import com.android.launcher3.util.LongArrayMap;
-import com.android.launcher3.util.Provider;
 
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 
 import java.util.ArrayList;
 import java.util.List;
 
-import static org.mockito.Matchers.isNull;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
 
 /**
  * Tests for {@link AddWorkspaceItemsTask}
  */
+@RunWith(AndroidJUnit4.class)
 public class AddWorkspaceItemsTaskTest extends BaseModelUpdateTaskTestCase {
 
     private final ComponentName mComponent1 = new ComponentName("a", "b");
@@ -39,9 +44,8 @@
     private ArrayList<Long> newScreens;
     private LongArrayMap<GridOccupancy> screenOccupancy;
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
+    @Before
+    public void initData() throws Exception {
         existingScreens = new ArrayList<>();
         screenOccupancy = new LongArrayMap<>();
         newScreens = new ArrayList<>();
@@ -55,13 +59,14 @@
         for (ItemInfo item : items) {
             list.add(Pair.create(item, null));
         }
-        return new AddWorkspaceItemsTask(Provider.of(list)) {
+        return new AddWorkspaceItemsTask(list) {
 
             @Override
             protected void updateScreens(Context context, ArrayList<Long> workspaceScreens) { }
         };
     }
 
+    @Test
     public void testFindSpaceForItem_prefers_second() {
         // First screen has only one hole of size 1
         int nextId = setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));
@@ -83,13 +88,12 @@
                 .isRegionVacant(spaceFound.second[0], spaceFound.second[1], 2, 3));
     }
 
+    @Test
     public void testFindSpaceForItem_adds_new_screen() throws Exception {
         // First screen has 2 holes of sizes 3x2 and 2x3
         setupWorkspaceWithHoles(1, 1, new Rect(2, 0, 5, 2), new Rect(0, 2, 2, 5));
         commitScreensToDb();
 
-        when(appState.getContext()).thenReturn(getMockContext());
-
         ArrayList<Long> oldScreens = new ArrayList<>(existingScreens);
         Pair<Long, int[]> spaceFound = newTask()
                 .findSpaceForItem(appState, bgDataModel, existingScreens, newScreens, 3, 3);
@@ -97,6 +101,7 @@
         assertTrue(newScreens.contains(spaceFound.first));
     }
 
+    @Test
     public void testAddItem_existing_item_ignored() throws Exception {
         ShortcutInfo info = new ShortcutInfo();
         info.intent = new Intent().setComponent(mComponent1);
@@ -105,12 +110,11 @@
         setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));
         commitScreensToDb();
 
-        when(appState.getContext()).thenReturn(getMockContext());
-
         // Nothing was added
         assertTrue(executeTaskForTest(newTask(info)).isEmpty());
     }
 
+    @Test
     public void testAddItem_some_items_added() throws Exception {
         ShortcutInfo info = new ShortcutInfo();
         info.intent = new Intent().setComponent(mComponent1);
@@ -122,8 +126,6 @@
         setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));
         commitScreensToDb();
 
-        when(appState.getContext()).thenReturn(getMockContext());
-
         executeTaskForTest(newTask(info, info2)).get(0).run();
         ArgumentCaptor<ArrayList> notAnimated = ArgumentCaptor.forClass(ArrayList.class);
         ArgumentCaptor<ArrayList> animated = ArgumentCaptor.forClass(ArrayList.class);
@@ -168,7 +170,7 @@
     }
 
     private void commitScreensToDb() throws Exception {
-        LauncherSettings.Settings.call(getMockContentResolver(),
+        LauncherSettings.Settings.call(mProviderRule.getResolver(),
                 LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
 
         Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
@@ -183,6 +185,6 @@
             v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
             ops.add(ContentProviderOperation.newInsert(uri).withValues(v).build());
         }
-        getMockContentResolver().applyBatch(LauncherProvider.AUTHORITY, ops);
+        mProviderRule.getResolver().applyBatch(LauncherProvider.AUTHORITY, ops);
     }
 }
diff --git a/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java b/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
index 3d03507..bbb6772 100644
--- a/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
+++ b/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
@@ -1,7 +1,9 @@
 package com.android.launcher3.model;
 
 import android.content.ComponentName;
+import android.content.ContentResolver;
 import android.content.Context;
+import android.content.ContextWrapper;
 import android.content.Intent;
 import android.content.pm.LauncherActivityInfo;
 import android.content.res.Resources;
@@ -11,7 +13,8 @@
 import android.os.UserHandle;
 import android.support.annotation.NonNull;
 import android.support.test.InstrumentationRegistry;
-import android.test.ProviderTestCase2;
+import android.support.test.rule.provider.ProviderTestRule;
+import android.support.test.runner.AndroidJUnit4;
 
 import com.android.launcher3.AllAppsList;
 import com.android.launcher3.AppFilter;
@@ -28,6 +31,8 @@
 import com.android.launcher3.util.Provider;
 import com.android.launcher3.util.TestLauncherProvider;
 
+import org.junit.Before;
+import org.junit.Rule;
 import org.mockito.ArgumentCaptor;
 
 import java.io.BufferedReader;
@@ -46,7 +51,12 @@
 /**
  * Base class for writing tests for Model update tasks.
  */
-public class BaseModelUpdateTaskTestCase extends ProviderTestCase2<TestLauncherProvider> {
+public class BaseModelUpdateTaskTestCase {
+
+    @Rule
+    public ProviderTestRule mProviderRule =
+            new ProviderTestRule.Builder(TestLauncherProvider.class, LauncherProvider.AUTHORITY)
+                    .build();
 
     public final HashMap<Class, HashMap<String, Field>> fieldCache = new HashMap<>();
 
@@ -63,14 +73,8 @@
     public AllAppsList allAppsList;
     public Callbacks callbacks;
 
-    public BaseModelUpdateTaskTestCase() {
-        super(TestLauncherProvider.class, LauncherProvider.AUTHORITY);
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
+    @Before
+    public void setUp() throws Exception {
         callbacks = mock(Callbacks.class);
         appState = mock(LauncherAppState.class);
         model = mock(LauncherModel.class);
@@ -83,7 +87,12 @@
         myUser = Process.myUserHandle();
 
         bgDataModel = new BgDataModel();
-        targetContext = InstrumentationRegistry.getTargetContext();
+        targetContext = new ContextWrapper(InstrumentationRegistry.getTargetContext()) {
+            @Override
+            public ContentResolver getContentResolver() {
+                return mProviderRule.getResolver();
+            }
+        };
         idp = new InvariantDeviceProfile();
         iconCache = new MyIconCache(targetContext, idp);
 
@@ -91,6 +100,8 @@
 
         when(appState.getIconCache()).thenReturn(iconCache);
         when(appState.getInvariantDeviceProfile()).thenReturn(idp);
+        when(appState.getContext()).thenReturn(targetContext);
+
     }
 
     /**
diff --git a/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java b/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
index d595e6c..ac9d319 100644
--- a/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
+++ b/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
@@ -1,23 +1,34 @@
 package com.android.launcher3.model;
 
+import android.support.test.runner.AndroidJUnit4;
+
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.ShortcutInfo;
 
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import java.util.Arrays;
 import java.util.HashSet;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+
 /**
  * Tests for {@link CacheDataUpdatedTask}
  */
+@RunWith(AndroidJUnit4.class)
 public class CacheDataUpdatedTaskTest extends BaseModelUpdateTaskTestCase {
 
     private static final String NEW_LABEL_PREFIX = "new-label-";
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
+    @Before
+    public void initData() throws Exception {
         initializeData("cache_data_updated_task_data");
         // Add dummy entries in the cache to simulate update
         for (ItemInfo info : bgDataModel.itemsIdMap) {
@@ -29,6 +40,7 @@
         return new CacheDataUpdatedTask(op, myUser, new HashSet<>(Arrays.asList(pkg)));
     }
 
+    @Test
     public void testCacheUpdate_update_apps() throws Exception {
         // Clear all icons from apps list so that its easy to check what was updated
         for (AppInfo info : allAppsList.data) {
@@ -52,6 +64,7 @@
         }
     }
 
+    @Test
     public void testSessionUpdate_ignores_normal_apps() throws Exception {
         executeTaskForTest(newTask(CacheDataUpdatedTask.OP_SESSION_UPDATE, "app1"));
 
@@ -59,6 +72,7 @@
         verifyUpdate();
     }
 
+    @Test
     public void testSessionUpdate_updates_pending_apps() throws Exception {
         executeTaskForTest(newTask(CacheDataUpdatedTask.OP_SESSION_UPDATE, "app3"));
 
diff --git a/tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java b/tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java
index fd62d36..b92f612 100644
--- a/tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java
+++ b/tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java
@@ -1,11 +1,16 @@
 package com.android.launcher3.model;
 
+import android.content.ContentResolver;
 import android.content.ContentValues;
+import android.content.Context;
+import android.content.ContextWrapper;
 import android.content.Intent;
 import android.database.Cursor;
 import android.graphics.Point;
-import android.test.ProviderTestCase2;
-import android.test.suitebuilder.annotation.MediumTest;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.rule.provider.ProviderTestRule;
+import android.support.test.runner.AndroidJUnit4;
 
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherModel;
@@ -15,15 +20,29 @@
 import com.android.launcher3.model.GridSizeMigrationTask.MultiStepMigrationTask;
 import com.android.launcher3.util.TestLauncherProvider;
 
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.LinkedList;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
 /**
  * Unit tests for {@link GridSizeMigrationTask}
  */
 @MediumTest
-public class GridSizeMigrationTaskTest extends ProviderTestCase2<TestLauncherProvider> {
+@RunWith(AndroidJUnit4.class)
+public class GridSizeMigrationTaskTest {
+
+    @Rule
+    public ProviderTestRule mProviderRule =
+            new ProviderTestRule.Builder(TestLauncherProvider.class, LauncherProvider.AUTHORITY)
+                    .build();
 
     private static final long DESKTOP = LauncherSettings.Favorites.CONTAINER_DESKTOP;
     private static final long HOTSEAT = LauncherSettings.Favorites.CONTAINER_HOTSEAT;
@@ -37,20 +56,25 @@
 
     private HashSet<String> mValidPackages;
     private InvariantDeviceProfile mIdp;
+    private Context mContext;
 
-    public GridSizeMigrationTaskTest() {
-        super(TestLauncherProvider.class, LauncherProvider.AUTHORITY);
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
+    @Before
+    public void setUp() throws Exception {
         mValidPackages = new HashSet<>();
         mValidPackages.add(TEST_PACKAGE);
 
         mIdp = new InvariantDeviceProfile();
+
+        mContext = new ContextWrapper(InstrumentationRegistry.getTargetContext()) {
+
+            @Override
+            public ContentResolver getContentResolver() {
+                return mProviderRule.getResolver();
+            }
+        };
     }
 
+    @Test
     public void testHotseatMigration_apps_dropped() throws Exception {
         long[] hotseatItems = {
                 addItem(APPLICATION, 0, HOTSEAT, 0, 0),
@@ -61,7 +85,7 @@
         };
 
         mIdp.numHotseatIcons = 3;
-        new GridSizeMigrationTask(getMockContext(), mIdp, mValidPackages, 5, 3)
+        new GridSizeMigrationTask(mContext, mIdp, mValidPackages, 5, 3)
                 .migrateHotseat();
         if (FeatureFlags.NO_ALL_APPS_ICON) {
             // First item is dropped as it has the least weight.
@@ -72,6 +96,7 @@
         }
     }
 
+    @Test
     public void testHotseatMigration_shortcuts_dropped() throws Exception {
         long[] hotseatItems = {
                 addItem(APPLICATION, 0, HOTSEAT, 0, 0),
@@ -82,7 +107,7 @@
         };
 
         mIdp.numHotseatIcons = 3;
-        new GridSizeMigrationTask(getMockContext(), mIdp, mValidPackages, 5, 3)
+        new GridSizeMigrationTask(mContext, mIdp, mValidPackages, 5, 3)
                 .migrateHotseat();
         if (FeatureFlags.NO_ALL_APPS_ICON) {
             // First item is dropped as it has the least weight.
@@ -98,7 +123,7 @@
         int total = 0;
 
         for (long id : sortedIds) {
-            Cursor c = getMockContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
+            Cursor c = mProviderRule.getResolver().query(LauncherSettings.Favorites.CONTENT_URI,
                     new String[]{LauncherSettings.Favorites._ID},
                     "container=-101 and screen=" + screenId, null, null, null);
 
@@ -116,13 +141,14 @@
         }
 
         // Verify that not other entry exist in the DB.
-        Cursor c = getMockContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
+        Cursor c = mProviderRule.getResolver().query(LauncherSettings.Favorites.CONTENT_URI,
                 new String[]{LauncherSettings.Favorites._ID},
                 "container=-101", null, null, null);
         assertEquals(total, c.getCount());
         c.close();
     }
 
+    @Test
     public void testWorkspace_empty_row_column_removed() throws Exception {
         long[][][] ids = createGrid(new int[][][]{{
                 {  0,  0, -1,  1},
@@ -131,7 +157,7 @@
                 {  5,  2, -1,  6},
         }});
 
-        new GridSizeMigrationTask(getMockContext(), mIdp, mValidPackages,
+        new GridSizeMigrationTask(mContext, mIdp, mValidPackages,
                 new Point(4, 4), new Point(3, 3)).migrateWorkspace();
 
         // Column 2 and row 2 got removed.
@@ -142,6 +168,7 @@
         }});
     }
 
+    @Test
     public void testWorkspace_new_screen_created() throws Exception {
         long[][][] ids = createGrid(new int[][][]{{
                 {  0,  0,  0,  1},
@@ -150,7 +177,7 @@
                 {  5,  2, -1,  6},
         }});
 
-        new GridSizeMigrationTask(getMockContext(), mIdp, mValidPackages,
+        new GridSizeMigrationTask(mContext, mIdp, mValidPackages,
                 new Point(4, 4), new Point(3, 3)).migrateWorkspace();
 
         // Items in the second column get moved to new screen
@@ -163,6 +190,7 @@
         }});
     }
 
+    @Test
     public void testWorkspace_items_merged_in_next_screen() throws Exception {
         long[][][] ids = createGrid(new int[][][]{{
                 {  0,  0,  0,  1},
@@ -174,7 +202,7 @@
                 {  3,  1, -1,  4},
         }});
 
-        new GridSizeMigrationTask(getMockContext(), mIdp, mValidPackages,
+        new GridSizeMigrationTask(mContext, mIdp, mValidPackages,
                 new Point(4, 4), new Point(3, 3)).migrateWorkspace();
 
         // Items in the second column of the first screen should get placed on the 3rd
@@ -190,6 +218,7 @@
         }});
     }
 
+    @Test
     public void testWorkspace_items_not_merged_in_next_screen() throws Exception {
         // First screen has 2 items that need to be moved, but second screen has only one
         // empty space after migration (top-left corner)
@@ -205,7 +234,7 @@
                 {  5,  2, -1,  6},
         }});
 
-        new GridSizeMigrationTask(getMockContext(), mIdp, mValidPackages,
+        new GridSizeMigrationTask(mContext, mIdp, mValidPackages,
                 new Point(4, 4), new Point(3, 3)).migrateWorkspace();
 
         // Items in the second column of the first screen should get placed on a new screen.
@@ -222,6 +251,7 @@
         }});
     }
 
+    @Test
     public void testWorkspace_first_row_blocked() throws Exception {
         // The first screen has one item on the 4th column which needs moving, as the first row
         // will be kept empty.
@@ -232,7 +262,7 @@
                 {  5,  2,  7, -1},
         }}, 0);
 
-        new GridSizeMigrationTask(getMockContext(), mIdp, mValidPackages,
+        new GridSizeMigrationTask(mContext, mIdp, mValidPackages,
                 new Point(4, 4), new Point(3, 4)).migrateWorkspace();
 
         // Items in the second column of the first screen should get placed on a new screen.
@@ -246,6 +276,7 @@
         }});
     }
 
+    @Test
     public void testWorkspace_items_moved_to_empty_first_row() throws Exception {
         // Items will get moved to the next screen to keep the first screen empty.
         long[][][] ids = createGrid(new int[][][]{{
@@ -255,7 +286,7 @@
                 {  5,  6,  7, -1},
         }}, 0);
 
-        new GridSizeMigrationTask(getMockContext(), mIdp, mValidPackages,
+        new GridSizeMigrationTask(mContext, mIdp, mValidPackages,
                 new Point(4, 4), new Point(3, 3)).migrateWorkspace();
 
         // Items in the second column of the first screen should get placed on a new screen.
@@ -281,7 +312,7 @@
      * @return the same grid representation where each entry is the corresponding item id.
      */
     private long[][][] createGrid(int[][][] typeArray, long startScreen) throws Exception {
-        LauncherSettings.Settings.call(getMockContentResolver(),
+        LauncherSettings.Settings.call(mProviderRule.getResolver(),
                 LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
         long[][][] ids = new long[typeArray.length][][];
 
@@ -290,13 +321,13 @@
             long screenId = startScreen + i;
 
             // Keep the screen id counter up to date
-            LauncherSettings.Settings.call(getMockContentResolver(),
+            LauncherSettings.Settings.call(mProviderRule.getResolver(),
                     LauncherSettings.Settings.METHOD_NEW_SCREEN_ID);
 
             ContentValues v = new ContentValues();
             v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
             v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
-            getMockContentResolver().insert(LauncherSettings.WorkspaceScreens.CONTENT_URI, v);
+            mProviderRule.getResolver().insert(LauncherSettings.WorkspaceScreens.CONTENT_URI, v);
 
             ids[i] = new long[typeArray[i].length][];
             for (int y = 0; y < typeArray[i].length; y++) {
@@ -320,7 +351,7 @@
      *            represent the workspace grid.
      */
     private void verifyWorkspace(long[][][] ids) {
-        ArrayList<Long> allScreens = LauncherModel.loadWorkspaceScreensDb(getMockContext());
+        ArrayList<Long> allScreens = LauncherModel.loadWorkspaceScreensDb(mContext);
         assertEquals(ids.length, allScreens.size());
         int total = 0;
 
@@ -330,7 +361,8 @@
                 for (int x = 0; x < ids[i][y].length; x++) {
                     long id = ids[i][y][x];
 
-                    Cursor c = getMockContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
+                    Cursor c = mProviderRule.getResolver().query(
+                            LauncherSettings.Favorites.CONTENT_URI,
                             new String[]{LauncherSettings.Favorites._ID},
                             "container=-100 and screen=" + screenId +
                                     " and cellX=" + x + " and cellY=" + y, null, null, null);
@@ -349,7 +381,7 @@
         }
 
         // Verify that not other entry exist in the DB.
-        Cursor c = getMockContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
+        Cursor c = mProviderRule.getResolver().query(LauncherSettings.Favorites.CONTENT_URI,
                 new String[]{LauncherSettings.Favorites._ID},
                 "container=-100", null, null, null);
         assertEquals(total, c.getCount());
@@ -362,7 +394,7 @@
      *             folder (where the type represents the number of items in the folder).
      */
     private long addItem(int type, long screen, long container, int x, int y) throws Exception {
-        long id = LauncherSettings.Settings.call(getMockContentResolver(),
+        long id = LauncherSettings.Settings.call(mProviderRule.getResolver(),
                 LauncherSettings.Settings.METHOD_NEW_ITEM_ID)
                 .getLong(LauncherSettings.Settings.EXTRA_VALUE);
 
@@ -387,16 +419,18 @@
             }
         }
 
-        getMockContentResolver().insert(LauncherSettings.Favorites.CONTENT_URI, values);
+        mProviderRule.getResolver().insert(LauncherSettings.Favorites.CONTENT_URI, values);
         return id;
     }
 
+    @Test
     public void testMultiStepMigration_small_to_large() throws Exception {
         MultiStepMigrationTaskVerifier verifier = new MultiStepMigrationTaskVerifier();
         verifier.migrate(new Point(3, 3), new Point(5, 5));
         verifier.assertCompleted();
     }
 
+    @Test
     public void testMultiStepMigration_large_to_small() throws Exception {
         MultiStepMigrationTaskVerifier verifier = new MultiStepMigrationTaskVerifier(
                 5, 5, 4, 4,
@@ -406,6 +440,7 @@
         verifier.assertCompleted();
     }
 
+    @Test
     public void testMultiStepMigration_zig_zag() throws Exception {
         MultiStepMigrationTaskVerifier verifier = new MultiStepMigrationTaskVerifier(
                 5, 7, 4, 7,
diff --git a/tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java b/tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java
index ed893c4..0a741c4 100644
--- a/tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java
+++ b/tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java
@@ -1,22 +1,30 @@
 package com.android.launcher3.model;
 
+import android.support.test.runner.AndroidJUnit4;
+
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.ShortcutInfo;
 import com.android.launcher3.compat.PackageInstallerCompat;
 import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
 
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import java.util.Arrays;
 import java.util.HashSet;
 
+import static org.junit.Assert.assertEquals;
+
 /**
  * Tests for {@link PackageInstallStateChangedTask}
  */
+@RunWith(AndroidJUnit4.class)
 public class PackageInstallStateChangedTaskTest extends BaseModelUpdateTaskTestCase {
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
+    @Before
+    public void initData() throws Exception {
         initializeData("package_install_state_change_task_data");
     }
 
@@ -26,6 +34,7 @@
         return new PackageInstallStateChangedTask(installInfo);
     }
 
+    @Test
     public void testSessionUpdate_ignore_installed() throws Exception {
         executeTaskForTest(newTask("app1", 30));
 
@@ -33,12 +42,14 @@
         verifyProgressUpdate(0);
     }
 
+    @Test
     public void testSessionUpdate_shortcuts_updated() throws Exception {
         executeTaskForTest(newTask("app3", 30));
 
         verifyProgressUpdate(30, 5L, 6L, 7L);
     }
 
+    @Test
     public void testSessionUpdate_widgets_updated() throws Exception {
         executeTaskForTest(newTask("app4", 30));
 
diff --git a/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java b/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
index 5858e13..5d417b5 100644
--- a/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
+++ b/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
@@ -3,23 +3,32 @@
 import android.content.ContentValues;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.MediumTest;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
 
 import com.android.launcher3.LauncherProvider.DatabaseHelper;
 import com.android.launcher3.LauncherSettings.Favorites;
 
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertEquals;
+
 /**
  * Tests for {@link RestoreDbTask}
  */
 @MediumTest
-public class RestoreDbTaskTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class RestoreDbTaskTest {
 
+    @Test
     public void testGetProfileId() throws Exception {
         SQLiteDatabase db = new MyDatabaseHelper(23).getWritableDatabase();
         assertEquals(23, new RestoreDbTask().getDefaultProfileId(db));
     }
 
+    @Test
     public void testMigrateProfileId() throws Exception {
         SQLiteDatabase db = new MyDatabaseHelper(42).getWritableDatabase();
         // Add some dummy data
@@ -57,7 +66,7 @@
         private final long mProfileId;
 
         MyDatabaseHelper(long profileId) {
-            super(getContext(), null, null);
+            super(InstrumentationRegistry.getContext(), null, null);
             mProfileId = profileId;
         }
 
@@ -66,6 +75,9 @@
             return mProfileId;
         }
 
+        @Override
+        protected void handleOneTimeDataUpgrade(SQLiteDatabase db) { }
+
         protected void onEmptyDbCreated() { }
     }
 }
diff --git a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
index d4d517a..87103d7 100644
--- a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
@@ -16,6 +16,7 @@
 package com.android.launcher3.ui.widget;
 
 import android.appwidget.AppWidgetHost;
+import android.appwidget.AppWidgetManager;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.ContentValues;
@@ -171,8 +172,9 @@
         // Widget has a valid Id now.
         assertEquals(0, mCursor.getInt(mCursor.getColumnIndex(LauncherSettings.Favorites.RESTORED))
                 & LauncherAppWidgetInfo.FLAG_ID_NOT_VALID);
-        assertNotNull(mWidgetManager.getAppWidgetInfo(mCursor.getInt(mCursor.getColumnIndex(
-                LauncherSettings.Favorites.APPWIDGET_ID))));
+        assertNotNull(AppWidgetManager.getInstance(mTargetContext)
+                .getAppWidgetInfo(mCursor.getInt(mCursor.getColumnIndex(
+                        LauncherSettings.Favorites.APPWIDGET_ID))));
     }
 
     @Test
@@ -297,7 +299,7 @@
         item.spanY = info.minSpanY;
         item.minSpanX = info.minSpanX;
         item.minSpanY = info.minSpanY;
-        item.user = info.getUser();
+        item.user = info.getProfile();
         item.cellX = 0;
         item.cellY = 1;
         item.container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
diff --git a/tests/src/com/android/launcher3/util/FocusLogicTest.java b/tests/src/com/android/launcher3/util/FocusLogicTest.java
index 79aed80..691d9bc 100644
--- a/tests/src/com/android/launcher3/util/FocusLogicTest.java
+++ b/tests/src/com/android/launcher3/util/FocusLogicTest.java
@@ -16,30 +16,24 @@
 
 package com.android.launcher3.util;
 
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
 import android.view.KeyEvent;
-import android.view.View;
 
-import com.android.launcher3.util.FocusLogic;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
 /**
  * Tests the {@link FocusLogic} class that handles key event based focus handling.
  */
 @SmallTest
-public final class FocusLogicTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public final class FocusLogicTest {
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        // Nothing to set up as this class only tests static methods.
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        // Nothing to tear down as this class only tests static methods.
-    }
-
+    @Test
     public void testShouldConsume() {
          assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_DPAD_LEFT));
          assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_DPAD_RIGHT));
@@ -51,12 +45,14 @@
          assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_PAGE_DOWN));
     }
 
+    @Test
     public void testCreateSparseMatrix() {
          // Either, 1) create a helper method to generate/instantiate all possible cell layout that
          // may get created in real world to test this method. OR 2) Move all the matrix
          // management routine to celllayout and write tests for them.
     }
 
+    @Test
     public void testMoveFromBottomRightToBottomLeft() {
         int[][] map = transpose(new int[][] {
                 {-1, 0, -1, -1, -1, -1},
@@ -69,6 +65,7 @@
         assertEquals(1, i);
     }
 
+    @Test
     public void testMoveFromBottomRightToTopLeft() {
         int[][] map = transpose(new int[][] {
                 {-1, 0, -1, -1, -1, -1},
@@ -81,6 +78,7 @@
         assertEquals(FocusLogic.NEXT_PAGE_FIRST_ITEM, i);
     }
 
+    @Test
     public void testMoveIntoHotseatWithEqualHotseatAndWorkspaceColumns() {
         // Test going from an icon right above the All Apps button to the All Apps button.
         int[][] map = transpose(new int[][] {
@@ -105,6 +103,7 @@
         assertEquals(4, i);
     }
 
+    @Test
     public void testMoveIntoHotseatWithExtraColumnForAllApps() {
         // Test going from an icon above and to the left
         // of the All Apps button to the All Apps button.
@@ -187,6 +186,7 @@
         assertEquals(12, i);
     }
 
+    @Test
     public void testCrossingAllAppsColumn() {
         // Test crossing from left to right in portrait.
         int[][] map = transpose(new int[][] {
diff --git a/tests/src/com/android/launcher3/util/GridOccupancyTest.java b/tests/src/com/android/launcher3/util/GridOccupancyTest.java
index 7d0fe71..fdd8e88 100644
--- a/tests/src/com/android/launcher3/util/GridOccupancyTest.java
+++ b/tests/src/com/android/launcher3/util/GridOccupancyTest.java
@@ -1,15 +1,23 @@
 package com.android.launcher3.util;
 
-import android.test.suitebuilder.annotation.SmallTest;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
 
-import junit.framework.TestCase;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
 /**
  * Unit tests for {@link GridOccupancy}
  */
 @SmallTest
-public class GridOccupancyTest extends TestCase {
+@RunWith(AndroidJUnit4.class)
+public class GridOccupancyTest {
 
+    @Test
     public void testFindVacantCell() {
         GridOccupancy grid = initGrid(4,
                 1, 1, 1, 0, 0,
@@ -30,6 +38,7 @@
         assertFalse(grid.findVacantCell(vacant, 3, 3));
     }
 
+    @Test
     public void testIsRegionVacant() {
         GridOccupancy grid = initGrid(4,
                 1, 1, 1, 0, 0,
diff --git a/tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java b/tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java
index 40b65e4..0185f13 100644
--- a/tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java
+++ b/tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java
@@ -22,13 +22,13 @@
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
+import android.support.v7.widget.RecyclerView;
 import android.view.LayoutInflater;
 
 import com.android.launcher3.IconCache;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.WidgetPreviewLoader;
-import com.android.launcher3.compat.AlphabeticIndexCompat;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 import com.android.launcher3.model.PackageItemInfo;
 import com.android.launcher3.model.WidgetItem;
@@ -40,8 +40,11 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
+import java.util.ArrayList;
+import java.util.Map;
+
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isNull;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
@@ -49,15 +52,12 @@
 @RunWith(AndroidJUnit4.class)
 public class WidgetsListAdapterTest {
 
-    private final String TAG = "WidgetsListAdapterTest";
-
     @Mock private LayoutInflater mMockLayoutInflater;
     @Mock private WidgetPreviewLoader mMockWidgetCache;
-    @Mock private WidgetsDiffReporter.NotifyListener mListener;
+    @Mock private RecyclerView.AdapterDataObserver mListener;
     @Mock private IconCache mIconCache;
 
     private WidgetsListAdapter mAdapter;
-    private AlphabeticIndexCompat mIndexCompat;
     private InvariantDeviceProfile mTestProfile;
     private Context mContext;
 
@@ -68,41 +68,39 @@
         mTestProfile = new InvariantDeviceProfile();
         mTestProfile.numRows = 5;
         mTestProfile.numColumns = 5;
-        mIndexCompat = new AlphabeticIndexCompat(mContext);
-        WidgetsDiffReporter reporter = new WidgetsDiffReporter(mIconCache);
-        reporter.setListener(mListener);
         mAdapter = new WidgetsListAdapter(mContext, mMockLayoutInflater, mMockWidgetCache,
-                mIndexCompat, null, null, reporter);
+                mIconCache, null, null);
+        mAdapter.registerAdapterDataObserver(mListener);
     }
 
     @Test
     public void test_notifyDataSetChanged() throws Exception {
         mAdapter.setWidgets(generateSampleMap(1));
-        verify(mListener, times(1)).notifyDataSetChanged();
+        verify(mListener, times(1)).onChanged();
     }
 
     @Test
     public void test_notifyItemInserted() throws Exception {
         mAdapter.setWidgets(generateSampleMap(1));
         mAdapter.setWidgets(generateSampleMap(2));
-        verify(mListener, times(1)).notifyDataSetChanged();
-        verify(mListener, times(1)).notifyItemInserted(1);
+        verify(mListener, times(1)).onChanged();
+        verify(mListener, times(1)).onItemRangeInserted(eq(1), eq(1));
     }
 
     @Test
     public void test_notifyItemRemoved() throws Exception {
         mAdapter.setWidgets(generateSampleMap(2));
         mAdapter.setWidgets(generateSampleMap(1));
-        verify(mListener, times(1)).notifyDataSetChanged();
-        verify(mListener, times(1)).notifyItemRemoved(1);
+        verify(mListener, times(1)).onChanged();
+        verify(mListener, times(1)).onItemRangeRemoved(eq(1), eq(1));
     }
 
     @Test
     public void testNotifyItemChanged_PackageIconDiff() throws Exception {
         mAdapter.setWidgets(generateSampleMap(1));
         mAdapter.setWidgets(generateSampleMap(1));
-        verify(mListener, times(1)).notifyDataSetChanged();
-        verify(mListener, times(1)).notifyItemChanged(0);
+        verify(mListener, times(1)).onChanged();
+        verify(mListener, times(1)).onItemRangeChanged(eq(0), eq(1), isNull());
     }
 
     @Test
@@ -125,10 +123,11 @@
      * @param num the number of WidgetItem the map should contain
      * @return
      */
-    private MultiHashMap<PackageItemInfo, WidgetItem> generateSampleMap(int num) {
-        MultiHashMap<PackageItemInfo, WidgetItem> newMap = new MultiHashMap();
-        if (num <= 0) return newMap;
+    private ArrayList<WidgetListRowEntry> generateSampleMap(int num) {
+        ArrayList<WidgetListRowEntry> result = new ArrayList<>();
+        if (num <= 0) return result;
 
+        MultiHashMap<PackageItemInfo, WidgetItem> newMap = new MultiHashMap();
         PackageManager pm = mContext.getPackageManager();
         AppWidgetManagerCompat widgetManager = AppWidgetManagerCompat.getInstance(mContext);
         for (AppWidgetProviderInfo widgetInfo : widgetManager.getAllProviders(null)) {
@@ -144,6 +143,10 @@
                 break;
             }
         }
-        return newMap;
+        for (Map.Entry<PackageItemInfo, ArrayList<WidgetItem>> entry : newMap.entrySet()) {
+            result.add(new WidgetListRowEntry(entry.getKey(), entry.getValue()));
+        }
+
+        return result;
     }
 }