Merge "Allowing the first screen to expand to the screen edge" into ub-launcher3-calgary
diff --git a/res/layout-land/launcher.xml b/res/layout-land/launcher.xml
index 3a361e2..1147326 100644
--- a/res/layout-land/launcher.xml
+++ b/res/layout-land/launcher.xml
@@ -51,12 +51,8 @@
             android:layout_gravity="right" />
 
         <include
-            android:id="@+id/app_info_drop_target_bar"
-            layout="@layout/drop_target_bar_vert_info" />
-
-        <include
-            android:id="@+id/search_drop_target_bar"
-            layout="@layout/drop_target_bar_vert_search" />
+            android:id="@+id/drop_target_bar"
+            layout="@layout/drop_target_bar_vert" />
 
         <include layout="@layout/overview_panel"
             android:id="@+id/overview_panel"
diff --git a/res/layout-port/launcher.xml b/res/layout-port/launcher.xml
index 4576e4d..fed99f3 100644
--- a/res/layout-port/launcher.xml
+++ b/res/layout-port/launcher.xml
@@ -60,15 +60,11 @@
         <com.android.launcher3.pageindicators.PageIndicatorLine
             android:id="@+id/page_indicator"
             android:layout_width="match_parent"
-            android:layout_height="1dp" />
+            android:layout_height="@dimen/dynamic_grid_page_indicator_height" />
 
         <include
-            android:id="@+id/app_info_drop_target_bar"
-            layout="@layout/drop_target_bar_horz_info" />
-
-        <include
-            android:id="@+id/search_drop_target_bar"
-            layout="@layout/drop_target_bar_horz_search" />
+            android:id="@+id/drop_target_bar"
+            layout="@layout/drop_target_bar_horz" />
 
         <include layout="@layout/widgets_view"
             android:id="@+id/widgets_view"
diff --git a/res/layout-sw720dp/launcher.xml b/res/layout-sw720dp/launcher.xml
index 0f755d8..23e673c 100644
--- a/res/layout-sw720dp/launcher.xml
+++ b/res/layout-sw720dp/launcher.xml
@@ -51,12 +51,8 @@
             android:layout_height="match_parent" />
 
         <include
-            android:id="@+id/app_info_drop_target_bar"
-            layout="@layout/drop_target_bar_horz_info" />
-
-        <include
-            android:id="@+id/search_drop_target_bar"
-            layout="@layout/drop_target_bar_horz_search" />
+            android:id="@+id/drop_target_bar"
+            layout="@layout/drop_target_bar_horz" />
 
         <include layout="@layout/overview_panel"
             android:id="@+id/overview_panel"
@@ -67,7 +63,7 @@
         <com.android.launcher3.pageindicators.PageIndicatorLine
             android:id="@+id/page_indicator"
             android:layout_width="match_parent"
-            android:layout_height="1dp" />
+            android:layout_height="@dimen/dynamic_grid_page_indicator_height" />
 
         <include layout="@layout/widgets_view"
             android:id="@+id/widgets_view"
diff --git a/res/layout/drop_target_bar_horz.xml b/res/layout/drop_target_bar_horz.xml
new file mode 100644
index 0000000..ee22d1e
--- /dev/null
+++ b/res/layout/drop_target_bar_horz.xml
@@ -0,0 +1,79 @@
+<?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.
+-->
+<com.android.launcher3.DropTargetBar
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="@dimen/dynamic_grid_drop_target_size"
+    android:layout_gravity="center_horizontal|top"
+    android:focusable="false">
+
+    <FrameLayout
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_weight="1" >
+
+        <!-- Delete target -->
+
+        <com.android.launcher3.DeleteDropTarget
+            launcher:hideParentOnDisable="true"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_gravity="center"
+            android:gravity="center"
+            android:id="@+id/delete_target_text"
+            style="@style/DropTargetButton"
+            android:text="@string/remove_drop_target_label" />
+    </FrameLayout>
+
+    <FrameLayout
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_weight="1" >
+
+        <!-- App Info -->
+
+        <com.android.launcher3.InfoDropTarget
+            launcher:hideParentOnDisable="true"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_gravity="center"
+            android:gravity="center"
+            android:id="@+id/info_target_text"
+            style="@style/DropTargetButton"
+            android:text="@string/app_info_drop_target_label" />
+    </FrameLayout>
+
+    <FrameLayout
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_weight="1" >
+
+        <!-- Uninstall target -->
+
+        <com.android.launcher3.UninstallDropTarget
+            launcher:hideParentOnDisable="true"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_gravity="center"
+            android:gravity="center"
+            android:id="@+id/uninstall_target_text"
+            style="@style/DropTargetButton"
+            android:text="@string/uninstall_drop_target_label" />
+    </FrameLayout>
+
+</com.android.launcher3.DropTargetBar>
\ No newline at end of file
diff --git a/res/layout/drop_target_bar_horz_info.xml b/res/layout/drop_target_bar_horz_info.xml
deleted file mode 100644
index 92a9b22..0000000
--- a/res/layout/drop_target_bar_horz_info.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     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.
--->
-<com.android.launcher3.AppInfoDropTargetBar xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="48dp"
-    android:layout_gravity="center_horizontal|bottom"
-    android:focusable="false" >
-
-    <FrameLayout
-        android:id="@+id/drag_target_bar"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent">
-
-        <!-- Info target -->
-
-        <com.android.launcher3.InfoDropTarget
-            android:layout_width="wrap_content"
-            android:layout_height="match_parent"
-            android:layout_gravity="center"
-            android:gravity="center"
-            android:id="@+id/info_target_text"
-            style="@style/DropTargetButton"
-            android:text="@string/app_info_drop_target_label" />
-    </FrameLayout>
-</com.android.launcher3.AppInfoDropTargetBar>
\ No newline at end of file
diff --git a/res/layout/drop_target_bar_horz_search.xml b/res/layout/drop_target_bar_horz_search.xml
deleted file mode 100644
index 7997801..0000000
--- a/res/layout/drop_target_bar_horz_search.xml
+++ /dev/null
@@ -1,69 +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.
--->
-<com.android.launcher3.SearchDropTargetBar xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:launcher="http://schemas.android.com/apk/res-auto"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:layout_gravity="center_horizontal|top"
-    android:focusable="false">
-
-    <!-- Drag specific targets container -->
-
-    <LinearLayout
-        android:id="@+id/drag_target_bar"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:layout_gravity="center" >
-
-        <FrameLayout
-            android:layout_width="0dp"
-            android:layout_height="match_parent"
-            android:layout_weight="1" >
-
-            <!-- Delete target -->
-
-            <com.android.launcher3.DeleteDropTarget
-                launcher:hideParentOnDisable="true"
-                android:layout_width="wrap_content"
-                android:layout_height="match_parent"
-                android:layout_gravity="center"
-                android:gravity="center"
-                android:id="@+id/delete_target_text"
-                style="@style/DropTargetButton"
-                android:text="@string/remove_drop_target_label" />
-        </FrameLayout>
-
-        <FrameLayout
-            android:layout_width="0dp"
-            android:layout_height="match_parent"
-            android:layout_weight="1" >
-
-            <!-- Uninstall target -->
-
-            <com.android.launcher3.UninstallDropTarget
-                launcher:hideParentOnDisable="true"
-                android:layout_width="wrap_content"
-                android:layout_height="match_parent"
-                android:layout_gravity="center"
-                android:gravity="center"
-                android:id="@+id/uninstall_target_text"
-                style="@style/DropTargetButton"
-                android:text="@string/uninstall_drop_target_label" />
-        </FrameLayout>
-    </LinearLayout>
-
-</com.android.launcher3.SearchDropTargetBar>
\ No newline at end of file
diff --git a/res/layout/drop_target_bar_vert.xml b/res/layout/drop_target_bar_vert.xml
new file mode 100644
index 0000000..10b1d7c
--- /dev/null
+++ b/res/layout/drop_target_bar_vert.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+<com.android.launcher3.DropTargetBar
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="@dimen/dynamic_grid_drop_target_size"
+    android:orientation="vertical"
+    android:layout_height="match_parent"
+    android:layout_gravity="left"
+    android:focusable="false"
+    android:paddingTop="@dimen/vert_drop_target_vertical_gap" >
+
+    <!-- Delete target -->
+    <com.android.launcher3.DeleteDropTarget
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/dynamic_grid_drop_target_size"
+        android:gravity="center"
+        android:paddingLeft="@dimen/vert_drop_target_horizontal_gap"
+        android:paddingRight="@dimen/vert_drop_target_horizontal_gap"
+        android:id="@+id/delete_target_text"
+        android:textColor="@android:color/white" />
+
+    <!-- Uninstall target -->
+    <com.android.launcher3.UninstallDropTarget
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/dynamic_grid_drop_target_size"
+        android:gravity="center"
+        android:paddingLeft="@dimen/vert_drop_target_horizontal_gap"
+        android:paddingRight="@dimen/vert_drop_target_horizontal_gap"
+        android:id="@+id/uninstall_target_text"
+        android:textColor="@android:color/white"
+        android:layout_marginTop="@dimen/vert_drop_target_vertical_gap"/>
+
+    <Space
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1" />
+
+    <!-- App Info -->
+    <com.android.launcher3.InfoDropTarget
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/dynamic_grid_drop_target_size"
+        android:gravity="center"
+        android:paddingLeft="@dimen/vert_drop_target_horizontal_gap"
+        android:paddingRight="@dimen/vert_drop_target_horizontal_gap"
+        android:id="@+id/info_target_text"
+        android:textColor="@android:color/white"
+        android:layout_marginBottom="64dp"/>
+
+</com.android.launcher3.DropTargetBar>
\ No newline at end of file
diff --git a/res/layout/drop_target_bar_vert_info.xml b/res/layout/drop_target_bar_vert_info.xml
deleted file mode 100644
index da33d1a..0000000
--- a/res/layout/drop_target_bar_vert_info.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     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.
--->
-<com.android.launcher3.AppInfoDropTargetBar xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="48dp"
-    android:layout_height="match_parent"
-    android:focusable="false" >
-
-    <FrameLayout
-        android:id="@+id/drag_target_bar"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent">
-
-        <!-- Info target -->
-        <com.android.launcher3.InfoDropTarget
-            android:layout_width="48dp"
-            android:layout_height="48dp"
-            android:layout_gravity="center_horizontal|bottom"
-            android:gravity="center"
-            android:paddingLeft="14dp"
-            android:paddingRight="14dp"
-            android:textColor="@android:color/white"
-            android:id="@+id/info_target_text" />
-    </FrameLayout>
-
-</com.android.launcher3.AppInfoDropTargetBar>
\ No newline at end of file
diff --git a/res/layout/drop_target_bar_vert_search.xml b/res/layout/drop_target_bar_vert_search.xml
deleted file mode 100644
index d5e41df..0000000
--- a/res/layout/drop_target_bar_vert_search.xml
+++ /dev/null
@@ -1,56 +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.
--->
-<com.android.launcher3.SearchDropTargetBar xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="48dp"
-    android:layout_height="match_parent"
-    android:focusable="false">
-
-    <!-- Drag specific targets container -->
-
-    <LinearLayout
-        android:id="@+id/drag_target_bar"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:orientation="vertical"
-        android:layout_gravity="center"
-        android:paddingTop="20dp">
-
-        <!-- Delete target -->
-        <com.android.launcher3.DeleteDropTarget
-            android:layout_width="48dp"
-            android:layout_height="48dp"
-            android:gravity="center"
-            android:paddingLeft="14dp"
-            android:paddingRight="14dp"
-            android:id="@+id/delete_target_text"
-            android:textColor="@android:color/white"
-            android:layout_marginBottom="10dp" />
-
-        <!-- Uninstall target -->
-        <com.android.launcher3.UninstallDropTarget
-            android:layout_width="48dp"
-            android:layout_height="48dp"
-            android:layout_gravity="center"
-            android:gravity="center"
-            android:paddingLeft="14dp"
-            android:paddingRight="14dp"
-            android:id="@+id/uninstall_target_text"
-            android:textColor="@android:color/white"
-            android:layout_marginTop="10dp"/>
-    </LinearLayout>
-
-</com.android.launcher3.SearchDropTargetBar>
\ No newline at end of file
diff --git a/res/layout/qsb_container.xml b/res/layout/qsb_container.xml
index 3de2876..55c7390 100644
--- a/res/layout/qsb_container.xml
+++ b/res/layout/qsb_container.xml
@@ -20,4 +20,11 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:id="@+id/qsb_container"
-        android:padding="0dp" />
\ No newline at end of file
+        android:padding="0dp" >
+
+    <fragment
+        android:name="com.android.launcher3.QsbContainerView$QsbFragment"
+        android:layout_width="match_parent"
+        android:tag="qsb_view"
+        android:layout_height="match_parent"/>
+</com.android.launcher3.QsbContainerView>
\ No newline at end of file
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 1169f44..8fa7586 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -76,10 +76,10 @@
     <string name="allow_rotation_desc" msgid="7635719920854330492">"Al girar el dispositivo"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"La configuración de pantalla actual no permite girar la pantalla"</string>
     <string name="package_state_unknown" msgid="7592128424511031410">"Desconocido"</string>
-    <string name="abandoned_clean_this" msgid="7610119707847920412">"Eliminar"</string>
+    <string name="abandoned_clean_this" msgid="7610119707847920412">"Quitar"</string>
     <string name="abandoned_search" msgid="891119232568284442">"Buscar"</string>
     <string name="abandoned_promises_title" msgid="7096178467971716750">"Esta aplicación no está instalada"</string>
-    <string name="abandoned_promise_explanation" msgid="3990027586878167529">"La aplicación de este icono no está instalada. Puedes eliminar el icono o buscar la aplicación e instalarla manualmente."</string>
+    <string name="abandoned_promise_explanation" msgid="3990027586878167529">"La aplicación de este icono no está instalada. Puedes quitar el icono o buscar la aplicación e instalarla manualmente."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"Descargando <xliff:g id="NAME">%1$s</xliff:g> (<xliff:g id="PROGRESS">%2$s</xliff:g> completado)"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"Esperando para instalar <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"Añadir a la pantalla de inicio"</string>
diff --git a/res/values-ka-rGE/strings.xml b/res/values-ka-rGE/strings.xml
index 682cc12..3cc1885 100644
--- a/res/values-ka-rGE/strings.xml
+++ b/res/values-ka-rGE/strings.xml
@@ -59,7 +59,7 @@
     <string name="migration_cling_description" msgid="2752413805582227644">"გსურთ, ძველი მთავარი ეკრანიდან ხატულების და საქაღ. იმპორტი?"</string>
     <string name="migration_cling_copy_apps" msgid="946331230090919440">"ხატულების კოპირება"</string>
     <string name="migration_cling_use_default" msgid="2626475813981258626">"სტანდარტული განლაგება"</string>
-    <string name="workspace_cling_longpress_title" msgid="9173998993909018310">"ფონები, ვიჯეტები, &amp; პარამეტრები"</string>
+    <string name="workspace_cling_longpress_title" msgid="9173998993909018310">"ფონები, ვიჯეტები &amp; პარამეტრები"</string>
     <string name="workspace_cling_longpress_description" msgid="6569028007301925917">"მოსარგებად, ხანგრძლივად შეეხეთ ფონს."</string>
     <string name="workspace_cling_longpress_dismiss" msgid="368660286867640874">"გასაგებია"</string>
     <string name="folder_opened" msgid="94695026776264709">"საქაღალდე გახსნილია, <xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
diff --git a/res/values-my-rMM/strings.xml b/res/values-my-rMM/strings.xml
index 9081e84..a1acc03 100644
--- a/res/values-my-rMM/strings.xml
+++ b/res/values-my-rMM/strings.xml
@@ -35,7 +35,7 @@
     <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g>ကို သွားပါ"</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="9110807029020582876">"အပ်ပလီကေးရှင်းများ"</string>
+    <string name="all_apps_button_label" msgid="9110807029020582876">"အက်ပ်များ"</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>
diff --git a/res/values-ne-rNP/strings.xml b/res/values-ne-rNP/strings.xml
index 169f3de..acebc33 100644
--- a/res/values-ne-rNP/strings.xml
+++ b/res/values-ne-rNP/strings.xml
@@ -59,7 +59,7 @@
     <string name="migration_cling_description" msgid="2752413805582227644">"आफ्नो पुरानो गृह स्क्रीनबाट अाईकन र फोल्डरहरू आयात गर्नुहोस्?"</string>
     <string name="migration_cling_copy_apps" msgid="946331230090919440">"ICONS प्रतिलिप गर्नुहोस्"</string>
     <string name="migration_cling_use_default" msgid="2626475813981258626">"START FRESH"</string>
-    <string name="workspace_cling_longpress_title" msgid="9173998993909018310">"वालपेपरहरू, विजेट; सेटिङहरू"</string>
+    <string name="workspace_cling_longpress_title" msgid="9173998993909018310">"वालपेपर, विजेट तथा सेटिङहरू"</string>
     <string name="workspace_cling_longpress_description" msgid="6569028007301925917">"अनुकूलन गर्नका लागि पृष्ठभूमिलाई ट्याप गरी थिचिरहनुहोस्"</string>
     <string name="workspace_cling_longpress_dismiss" msgid="368660286867640874">"बुँझें"</string>
     <string name="folder_opened" msgid="94695026776264709">"फोल्डर खुल्यो <xliff:g id="WIDTH">%1$d</xliff:g> बाट <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
diff --git a/res/values-pa-rIN/strings.xml b/res/values-pa-rIN/strings.xml
index 47bb556..8e8e6ea 100644
--- a/res/values-pa-rIN/strings.xml
+++ b/res/values-pa-rIN/strings.xml
@@ -30,7 +30,7 @@
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"ਡਬਲ-ਟੈਪ &amp; ਇੱਕ ਵਿਜੇਟ ਚੁਣਨ ਲਈ ਹੋਲਡ ਕਰੋ ਅਤੇ ਕਸਟਮ ਕਿਰਿਆਵਾਂ ਵਰਤੋ।"</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
     <string name="all_apps_search_bar_hint" msgid="6705987535534678581">"ਐਪਾਂ ਖੋਜੋ…"</string>
-    <string name="all_apps_loading_message" msgid="7557140873644765180">"ਐਪਸ ਲੋਡ ਕਰ ਰਿਹਾ ਹੈ..."</string>
+    <string name="all_apps_loading_message" msgid="7557140873644765180">"ਐਪਾਂ ਨੂੰ ਲੋਡ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" ਨਾਲ ਮਿਲਦੀਆਂ ਕੋਈ ਵੀ ਐਪਾਂ ਨਹੀਂ ਮਿਲੀਆਂ"</string>
     <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g> ਤੇ ਜਾਓ"</string>
     <string name="out_of_space" msgid="4691004494942118364">"ਇਸ ਹੋਮ ਸਕ੍ਰੀਨ ਲਈ ਹੋਰ ਖਾਲੀ ਸਥਾਨ ਨਹੀਂ ਹੈ।"</string>
@@ -40,12 +40,12 @@
     <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="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="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="gadget_error_text" msgid="6081085226050792095">"ਵਿਜੇਟ ਲੋਡ ਕਰਨ ਵਿੱਚ ਸਮੱਸਿਆ"</string>
     <string name="gadget_setup_text" msgid="8274003207686040488">"ਸਥਾਪਤ ਕਰੋ"</string>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index 5b2eb27..19f7a77 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -62,7 +62,7 @@
     <string name="migration_cling_copy_apps" msgid="946331230090919440">"NAKILI IKONI"</string>
     <string name="migration_cling_use_default" msgid="2626475813981258626">"ANZA UPYA"</string>
     <string name="workspace_cling_longpress_title" msgid="9173998993909018310">"Mandhari, wijeti na mipangilio"</string>
-    <string name="workspace_cling_longpress_description" msgid="6569028007301925917">"Gusa na ushikilie mandhari ili uweke mapendeleo"</string>
+    <string name="workspace_cling_longpress_description" msgid="6569028007301925917">"Gusa na ushikilie mandhari ili ubadilishe upendavyo"</string>
     <string name="workspace_cling_longpress_dismiss" msgid="368660286867640874">"NIMEELEWA"</string>
     <string name="folder_opened" msgid="94695026776264709">"Folda imefunguliwa, <xliff:g id="WIDTH">%1$d</xliff:g> kwa <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Gonga ili ufunge folda"</string>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 7510fafd..35ee58e 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -17,19 +17,7 @@
 <resources>
 <!-- Dynamic Grid -->
     <dimen name="dynamic_grid_edge_margin">6dp</dimen>
-    <dimen name="dynamic_grid_search_bar_height">48dp</dimen>
-    <!-- We want 46dp extra for the tall search bar. -->
-    <dimen name="dynamic_grid_search_bar_height_tall">94dp</dimen>
-    <dimen name="qsb_internal_padding_top">8dp</dimen>
-    <dimen name="qsb_internal_padding_bottom">8dp</dimen>
-    <dimen name="dynamic_grid_search_bar_extra_top_padding">0dp</dimen>
-    <!-- Reduce the space between the status bar and the search bar when the search bar is tall -->
-    <dimen name="dynamic_grid_search_bar_negative_top_padding_short">-4dp</dimen>
-    <dimen name="dynamic_grid_search_bar_bottom_padding">4dp</dimen>
-    <!-- Reduce the padding between the search bar and workspace when the search bar is tall -->
-    <dimen name="dynamic_grid_search_bar_bottom_negative_padding_short">-6dp</dimen>
-    <dimen name="dynamic_grid_search_bar_bottom_padding_tablet">16dp</dimen>
-    <dimen name="dynamic_grid_page_indicator_height">20dp</dimen>
+    <dimen name="dynamic_grid_page_indicator_height">1dp</dimen>
     <dimen name="dynamic_grid_icon_drawable_padding">4dp</dimen>
     <dimen name="dynamic_grid_workspace_page_spacing">8dp</dimen>
     <dimen name="dynamic_grid_overview_min_icon_zone_height">80dp</dimen>
@@ -37,6 +25,15 @@
     <dimen name="dynamic_grid_overview_bar_item_width">80dp</dimen>
     <dimen name="dynamic_grid_overview_bar_spacer_width">20dp</dimen>
 
+    <dimen name="dynamic_grid_workspace_top_padding">12dp</dimen>
+    <!-- Minimum space between workspace and hotseat in spring loaded mode -->
+    <dimen name="dynamic_grid_min_spring_loaded_space">8dp</dimen>
+
+<!-- Drop target bar -->
+    <dimen name="dynamic_grid_drop_target_size">48dp</dimen>
+    <dimen name="vert_drop_target_vertical_gap">20dp</dimen>
+    <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>
diff --git a/src/com/android/launcher3/AppInfoDropTargetBar.java b/src/com/android/launcher3/AppInfoDropTargetBar.java
deleted file mode 100644
index e06f941..0000000
--- a/src/com/android/launcher3/AppInfoDropTargetBar.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3;
-
-import android.content.Context;
-import android.util.AttributeSet;
-
-import com.android.launcher3.dragndrop.DragController;
-
-public class AppInfoDropTargetBar extends BaseDropTargetBar {
-    private ButtonDropTarget mAppInfoDropTarget;
-
-    public AppInfoDropTargetBar(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public AppInfoDropTargetBar(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-
-        // Get the individual components
-        mAppInfoDropTarget = (ButtonDropTarget) mDropTargetBar.findViewById(R.id.info_target_text);
-
-        mAppInfoDropTarget.setDropTargetBar(this);
-    }
-
-    @Override
-    public void setup(Launcher launcher, DragController dragController) {
-        dragController.addDragListener(this);
-
-        dragController.addDragListener(mAppInfoDropTarget);
-        dragController.addDropTarget(mAppInfoDropTarget);
-
-        mAppInfoDropTarget.setLauncher(launcher);
-    }
-
-    @Override
-    public void showDropTargets() {
-        animateDropTargetBarToAlpha(1f, DEFAULT_DRAG_FADE_DURATION);
-    }
-
-    @Override
-    public void hideDropTargets() {
-        animateDropTargetBarToAlpha(0f, DEFAULT_DRAG_FADE_DURATION);
-    }
-
-    private void animateDropTargetBarToAlpha(float alpha, int duration) {
-        resetAnimation(duration);
-        if (duration > 0) {
-            animateAlpha(mDropTargetBar, alpha, DEFAULT_INTERPOLATOR);
-            mCurrentAnimation.start();
-        } else {
-            mDropTargetBar.setAlpha(alpha);
-            AlphaUpdateListener.updateVisibility(mDropTargetBar, mAccessibilityEnabled);
-        }
-    }
-
-    @Override
-    public void enableAccessibleDrag(boolean enable) {
-        mAppInfoDropTarget.enableAccessibleDrag(enable);
-    }
-}
diff --git a/src/com/android/launcher3/BaseContainerView.java b/src/com/android/launcher3/BaseContainerView.java
index d0cacd3..a9ef43d 100644
--- a/src/com/android/launcher3/BaseContainerView.java
+++ b/src/com/android/launcher3/BaseContainerView.java
@@ -24,6 +24,9 @@
 import android.view.View;
 import android.widget.FrameLayout;
 
+import com.android.launcher3.allapps.AllAppsContainerView;
+import com.android.launcher3.config.FeatureFlags;
+
 /**
  * A base container view, which supports resizing.
  */
@@ -48,7 +51,11 @@
         super(context, attrs, defStyleAttr);
 
         int width = ((Launcher) context).getDeviceProfile().availableWidthPx;
-        mHorizontalPadding = DeviceProfile.getContainerPadding(context, width);
+        if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && (this instanceof AllAppsContainerView)) {
+            mHorizontalPadding = 0;
+        } else {
+            mHorizontalPadding = DeviceProfile.getContainerPadding(context, width);
+        }
 
         TypedArray a = context.obtainStyledAttributes(attrs,
                 R.styleable.BaseContainerView, defStyleAttr, 0);
diff --git a/src/com/android/launcher3/BaseDropTargetBar.java b/src/com/android/launcher3/BaseDropTargetBar.java
deleted file mode 100644
index 9b38623..0000000
--- a/src/com/android/launcher3/BaseDropTargetBar.java
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3;
-
-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.util.AttributeSet;
-import android.view.View;
-import android.view.accessibility.AccessibilityManager;
-import android.view.animation.AccelerateInterpolator;
-import android.widget.FrameLayout;
-
-import com.android.launcher3.dragndrop.DragController;
-
-/**
- * Base class for drop target bars (where you can drop apps to do actions such as uninstall).
- */
-public abstract class BaseDropTargetBar extends FrameLayout implements DragController.DragListener {
-    protected static final int DEFAULT_DRAG_FADE_DURATION = 175;
-    protected static final TimeInterpolator DEFAULT_INTERPOLATOR = new AccelerateInterpolator();
-
-    protected View mDropTargetBar;
-    protected boolean mAccessibilityEnabled = false;
-
-    protected AnimatorSet mCurrentAnimation;
-    protected boolean mDeferOnDragEnd;
-
-    public BaseDropTargetBar(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public BaseDropTargetBar(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-
-        mDropTargetBar = findViewById(R.id.drag_target_bar);
-
-        // Create the various fade animations
-        mDropTargetBar.setAlpha(0f);
-    }
-
-    /**
-     * Convenience method to animate the alpha of a view.
-     */
-    protected void animateAlpha(View v, float alpha, TimeInterpolator interpolator) {
-        if (Float.compare(v.getAlpha(), alpha) != 0) {
-            ObjectAnimator anim = ObjectAnimator.ofFloat(v, View.ALPHA, alpha);
-            anim.setInterpolator(interpolator);
-            anim.addListener(new ViewVisiblilyUpdateHandler(v));
-            mCurrentAnimation.play(anim);
-        }
-    }
-
-    protected void resetAnimation(int newAnimationDuration) {
-        // Update the accessibility state
-        AccessibilityManager am = (AccessibilityManager)
-                getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
-        mAccessibilityEnabled = am.isEnabled();
-
-        // Cancel any existing animation
-        if (mCurrentAnimation != null) {
-            mCurrentAnimation.cancel();
-            mCurrentAnimation = null;
-        }
-
-        if (newAnimationDuration > 0) {
-            mCurrentAnimation = new AnimatorSet();
-            mCurrentAnimation.setDuration(newAnimationDuration);
-        }
-    }
-
-    /*
-     * DragController.DragListener implementation
-     */
-    @Override
-    public void onDragStart(DragSource source, ItemInfo info, int dragAction) {
-        showDropTargets();
-    }
-
-    /**
-     * This is called to defer hiding the delete drop target until the drop animation has completed,
-     * instead of hiding immediately when the drag has ended.
-     */
-    protected void deferOnDragEnd() {
-        mDeferOnDragEnd = true;
-    }
-
-    @Override
-    public void onDragEnd() {
-        if (!mDeferOnDragEnd) {
-            hideDropTargets();
-        } else {
-            mDeferOnDragEnd = false;
-        }
-    }
-
-    public abstract void showDropTargets();
-
-    public abstract void hideDropTargets();
-
-    public abstract void enableAccessibleDrag(boolean enable);
-
-    public abstract void setup(Launcher launcher, DragController dragController);
-
-    private class ViewVisiblilyUpdateHandler extends AnimatorListenerAdapter {
-        private final View mView;
-
-        ViewVisiblilyUpdateHandler(View v) {
-            mView = v;
-        }
-
-        @Override
-        public void onAnimationStart(Animator animation) {
-            // Ensure that the view is visible for the animation
-            mView.setVisibility(View.VISIBLE);
-        }
-
-        @Override
-        public void onAnimationEnd(Animator animation){
-            AlphaUpdateListener.updateVisibility(mView, mAccessibilityEnabled);
-        }
-
-    }
-}
diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java
index 43afbe5..61ac713 100644
--- a/src/com/android/launcher3/ButtonDropTarget.java
+++ b/src/com/android/launcher3/ButtonDropTarget.java
@@ -55,10 +55,10 @@
     private static final int DRAG_VIEW_DROP_DURATION = 285;
 
     private final boolean mHideParentOnDisable;
+    protected final Launcher mLauncher;
 
-    protected Launcher mLauncher;
     private int mBottomDragPadding;
-    protected BaseDropTargetBar mDropTargetBar;
+    protected DropTargetBar mDropTargetBar;
 
     /** Whether this drop target is active for the current drag */
     protected boolean mActive;
@@ -80,6 +80,8 @@
 
     public ButtonDropTarget(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
+        mLauncher = (Launcher) context;
+
         Resources resources = getResources();
         mBottomDragPadding = resources.getDimensionPixelSize(R.dimen.drop_target_drag_padding);
 
@@ -109,11 +111,7 @@
         }
     }
 
-    public void setLauncher(Launcher launcher) {
-        mLauncher = launcher;
-    }
-
-    public void setDropTargetBar(BaseDropTargetBar dropTargetBar) {
+    public void setDropTargetBar(DropTargetBar dropTargetBar) {
         mDropTargetBar = dropTargetBar;
     }
 
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 8d11aaa..4a550ed 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -70,7 +70,10 @@
     public final Rect defaultWidgetPadding;
     private final int pageIndicatorHeightPx;
     private final int defaultPageSpacingPx;
+    private final int topWorkspacePadding;
     private float dragViewScale;
+    public float workspaceSpringLoadShrinkFactor;
+    public final int workspaceSpringLoadedBottomSpace;
 
     // Workspace icons
     public int iconSizePx;
@@ -92,8 +95,7 @@
     public int hotseatCellWidthPx;
     public int hotseatCellHeightPx;
     public int hotseatIconSizePx;
-    private int normalHotseatBarHeightPx, shortHotseatBarHeightPx;
-    private int hotseatBarHeightPx; // One of the above.
+    private int hotseatBarHeightPx;
 
     // All apps
     public int allAppsNumCols;
@@ -102,15 +104,8 @@
     public final int allAppsIconSizePx;
     public final float allAppsIconTextSizeSp;
 
-    // QSB
-    private int searchBarWidgetInternalPaddingTop, searchBarWidgetInternalPaddingBottom;
-    private int searchBarTopPaddingPx;
-    private int tallSearchBarNegativeTopPaddingPx, normalSearchBarTopExtraPaddingPx;
-    private int searchBarTopExtraPaddingPx; // One of the above.
-    private int normalSearchBarBottomPaddingPx, tallSearchBarBottomPaddingPx;
-    private int searchBarBottomPaddingPx; // One of the above.
-    private int normalSearchBarSpaceHeightPx, tallSearchBarSpaceHeightPx;
-    private int searchBarSpaceHeightPx; // One of the above.
+    // Drop Target
+    public int dropTargetBarSizePx;
 
     public DeviceProfile(Context context, InvariantDeviceProfile inv,
             Point minSize, Point maxSize,
@@ -140,6 +135,8 @@
                 res.getDimensionPixelSize(R.dimen.dynamic_grid_page_indicator_height);
         defaultPageSpacingPx =
                 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 =
@@ -152,6 +149,9 @@
                 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);
+        workspaceSpringLoadedBottomSpace =
+                res.getDimensionPixelSize(R.dimen.dynamic_grid_min_spring_loaded_space);
 
         // AllApps uses the original non-scaled icon text size
         allAppsIconTextSizeSp = inv.iconTextSize;
@@ -195,7 +195,7 @@
         float usedHeight = (cellHeightPx * inv.numRows);
 
         // We only care about the top and bottom workspace padding, which is not affected by RTL.
-        Rect workspacePadding = getWorkspacePadding(false /* isLayoutRtl */);
+        Rect workspacePadding = getWorkspacePadding();
         int maxHeight = (availableHeightPx - workspacePadding.top - workspacePadding.bottom);
         if (usedHeight > maxHeight) {
             scale = maxHeight / usedHeight;
@@ -211,33 +211,6 @@
         iconDrawablePaddingPx = drawablePadding;
         hotseatIconSizePx = (int) (Utilities.pxFromDp(inv.hotseatIconSize, dm) * scale);
 
-        // Search Bar
-        normalSearchBarSpaceHeightPx = res.getDimensionPixelSize(
-                R.dimen.dynamic_grid_search_bar_height);
-        tallSearchBarSpaceHeightPx = res.getDimensionPixelSize(
-                R.dimen.dynamic_grid_search_bar_height_tall);
-        searchBarWidgetInternalPaddingTop = res.getDimensionPixelSize(
-                R.dimen.qsb_internal_padding_top);
-        searchBarWidgetInternalPaddingBottom = res.getDimensionPixelSize(
-                R.dimen.qsb_internal_padding_bottom);
-        normalSearchBarTopExtraPaddingPx = res.getDimensionPixelSize(
-                R.dimen.dynamic_grid_search_bar_extra_top_padding);
-        tallSearchBarNegativeTopPaddingPx = res.getDimensionPixelSize(
-                R.dimen.dynamic_grid_search_bar_negative_top_padding_short);
-        if (isTablet && !isVerticalBarLayout()) {
-            searchBarTopPaddingPx = searchBarWidgetInternalPaddingTop;
-            normalSearchBarBottomPaddingPx = searchBarWidgetInternalPaddingBottom +
-                    res.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_bottom_padding_tablet);
-            tallSearchBarBottomPaddingPx = normalSearchBarBottomPaddingPx;
-        } else {
-            searchBarTopPaddingPx = searchBarWidgetInternalPaddingTop;
-            normalSearchBarBottomPaddingPx = searchBarWidgetInternalPaddingBottom +
-                    res.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_bottom_padding);
-            tallSearchBarBottomPaddingPx = searchBarWidgetInternalPaddingBottom
-                    + res.getDimensionPixelSize(
-                    R.dimen.dynamic_grid_search_bar_bottom_negative_padding_short);
-        }
-
         // Calculate the actual text height
         Paint textPaint = new Paint();
         textPaint.setTextSize(iconTextSizePx);
@@ -249,11 +222,22 @@
         dragViewScale = (iconSizePx + scaleDps) / iconSizePx;
 
         // Hotseat
-        normalHotseatBarHeightPx = iconSizePx + 4 * edgeMarginPx;
-        shortHotseatBarHeightPx = iconSizePx + 2 * edgeMarginPx;
+        hotseatBarHeightPx = iconSizePx + 4 * edgeMarginPx;
         hotseatCellWidthPx = iconSizePx;
         hotseatCellHeightPx = iconSizePx;
 
+        if (!isVerticalBarLayout()) {
+            int expectedWorkspaceHeight = availableHeightPx - hotseatBarHeightPx
+                    - pageIndicatorHeightPx - topWorkspacePadding;
+            float minRequiredHeight = dropTargetBarSizePx + workspaceSpringLoadedBottomSpace;
+            workspaceSpringLoadShrinkFactor = Math.min(
+                    res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f,
+                    1 - (minRequiredHeight / expectedWorkspaceHeight));
+        } else {
+            workspaceSpringLoadShrinkFactor =
+                    res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f;
+        }
+
         // Folder
         int folderCellPadding = isTablet || isLandscape ? 6 * edgeMarginPx : 3 * edgeMarginPx;
         // Don't let the folder get too close to the edges of the screen.
@@ -281,60 +265,33 @@
         allAppsNumPredictiveCols = numPredictiveAppCols;
     }
 
-    /** Returns the amount of extra space to allocate to the search bar for vertical padding. */
-    private int getSearchBarTotalVerticalPadding() {
-        return searchBarTopPaddingPx + searchBarTopExtraPaddingPx + searchBarBottomPaddingPx;
-    }
-
     /** Returns the width and height of the search bar, ignoring any padding. */
-    public Point getSearchBarDimensForWidgetOpts(Resources res) {
-        Rect searchBarBounds = getSearchBarBounds(Utilities.isRtl(res));
+    public Point getSearchBarDimensForWidgetOpts() {
         if (isVerticalBarLayout()) {
-            return new Point(searchBarBounds.width(), searchBarBounds.height());
-        }
-        int widgetInternalPadding = searchBarWidgetInternalPaddingTop +
-                searchBarWidgetInternalPaddingBottom;
-        return new Point(searchBarBounds.width(), searchBarSpaceHeightPx + widgetInternalPadding);
-    }
-
-    /** Returns the search bar bounds in the current orientation */
-    public Rect getSearchBarBounds(boolean isLayoutRtl) {
-        Rect bounds = new Rect();
-        if (isVerticalBarLayout()) {
-            if (isLayoutRtl) {
-                bounds.set(availableWidthPx - normalSearchBarSpaceHeightPx, edgeMarginPx,
-                        availableWidthPx, availableHeightPx - edgeMarginPx);
-            } else {
-                bounds.set(0, edgeMarginPx, normalSearchBarSpaceHeightPx,
-                        availableHeightPx - edgeMarginPx);
-            }
+            return new Point(dropTargetBarSizePx, availableHeightPx - 2 * edgeMarginPx);
         } else {
-            int boundsBottom = searchBarSpaceHeightPx + getSearchBarTotalVerticalPadding();
+            int gap;
             if (isTablet) {
                 // Pad the left and right of the workspace to ensure consistent spacing
                 // between all icons
                 int width = getCurrentWidth();
                 // XXX: If the icon size changes across orientations, we will have to take
                 //      that into account here too.
-                int gap = (int) ((width - 2 * edgeMarginPx -
-                        (inv.numColumns * cellWidthPx)) / (2 * (inv.numColumns + 1)));
-                bounds.set(edgeMarginPx + gap, 0,
-                        availableWidthPx - (edgeMarginPx + gap), boundsBottom);
+                gap = ((width - 2 * edgeMarginPx
+                        - (inv.numColumns * cellWidthPx)) / (2 * (inv.numColumns + 1)))
+                        + edgeMarginPx;
             } else {
-                bounds.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left,
-                        0,
-                        availableWidthPx - (desiredWorkspaceLeftRightMarginPx -
-                        defaultWidgetPadding.right), boundsBottom);
+                gap = desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.right;
             }
+            return new Point(availableWidthPx - 2 * gap, dropTargetBarSizePx);
         }
-        return bounds;
     }
 
     public Point getCellSize() {
         Point result = new Point();
         // Since we are only concerned with the overall padding, layout direction does
         // not matter.
-        Rect padding = getWorkspacePadding(false /* isLayoutRtl */ );
+        Rect padding = getWorkspacePadding();
         result.x = calculateCellWidth(availableWidthPx - padding.left - padding.right,
                 inv.numColumns);
         result.y = calculateCellHeight(availableHeightPx - padding.top - padding.bottom,
@@ -343,20 +300,13 @@
     }
 
     /** Returns the workspace padding in the specified orientation */
-    public Rect getWorkspacePadding(boolean isLayoutRtl) {
-        Rect searchBarBounds = getSearchBarBounds(isLayoutRtl);
+    public Rect getWorkspacePadding() {
         Rect padding = new Rect();
         if (isVerticalBarLayout()) {
-            // Pad the left and right of the workspace with search/hotseat bar sizes
-            if (isLayoutRtl) {
-                padding.set(normalHotseatBarHeightPx, edgeMarginPx,
-                        searchBarBounds.width(), edgeMarginPx);
-            } else {
-                padding.set(searchBarBounds.width(), edgeMarginPx,
-                        normalHotseatBarHeightPx, edgeMarginPx);
-            }
+            // in case of isVerticalBarLayout, the hotseat is always on the right and the drop
+            // target bar is on the left, independent of the layout direction.
+            padding.set(dropTargetBarSizePx, edgeMarginPx, hotseatBarHeightPx, edgeMarginPx);
         } else {
-            int paddingTop = searchBarBounds.bottom;
             int paddingBottom = hotseatBarHeightPx + pageIndicatorHeightPx;
             if (isTablet) {
                 // Pad the left and right of the workspace to ensure consistent spacing
@@ -369,14 +319,14 @@
                         ((inv.numColumns - 1) * gapScale * cellWidthPx)));
                 availablePaddingX = (int) Math.min(availablePaddingX,
                             width * MAX_HORIZONTAL_PADDING_PERCENT);
-                int availablePaddingY = Math.max(0, height - paddingTop - paddingBottom
+                int availablePaddingY = Math.max(0, height - topWorkspacePadding - paddingBottom
                         - (int) (2 * inv.numRows * cellHeightPx));
-                padding.set(availablePaddingX / 2, paddingTop + availablePaddingY / 2,
+                padding.set(availablePaddingX / 2, topWorkspacePadding + availablePaddingY / 2,
                         availablePaddingX / 2, paddingBottom + availablePaddingY / 2);
             } else {
                 // Pad the top and bottom of the workspace with search/hotseat bar sizes
                 padding.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left,
-                        paddingTop,
+                        topWorkspacePadding,
                         desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.right,
                         paddingBottom);
             }
@@ -384,7 +334,7 @@
         return padding;
     }
 
-    private int getWorkspacePageSpacing(boolean isLayoutRtl) {
+    private int getWorkspacePageSpacing() {
         if (isVerticalBarLayout() || isLargeTablet) {
             // In landscape mode the page spacing is set to the default.
             return defaultPageSpacingPx;
@@ -392,7 +342,7 @@
             // In portrait, we want the pages spaced such that there is no
             // overhang of the previous / next page into the current page viewport.
             // We assume symmetrical padding in portrait mode.
-            return Math.max(defaultPageSpacingPx, 2 * getWorkspacePadding(isLayoutRtl).left);
+            return Math.max(defaultPageSpacingPx, 2 * getWorkspacePadding().left);
         }
     }
 
@@ -444,50 +394,28 @@
         return visibleChildren;
     }
 
-    // TODO(twickham): b/25154513
-    public void setSearchBarHeight(int searchBarHeight) {
-        if (searchBarHeight == LauncherCallbacks.SEARCH_BAR_HEIGHT_TALL) {
-            hotseatBarHeightPx = shortHotseatBarHeightPx;
-            searchBarSpaceHeightPx = tallSearchBarSpaceHeightPx;
-            searchBarBottomPaddingPx = tallSearchBarBottomPaddingPx;
-            searchBarTopExtraPaddingPx = isPhone ? tallSearchBarNegativeTopPaddingPx
-                    : normalSearchBarTopExtraPaddingPx;
-        } else {
-            hotseatBarHeightPx = normalHotseatBarHeightPx;
-            searchBarSpaceHeightPx = normalSearchBarSpaceHeightPx;
-            searchBarBottomPaddingPx = normalSearchBarBottomPaddingPx;
-            searchBarTopExtraPaddingPx = normalSearchBarTopExtraPaddingPx;
-        }
-    }
-
     public void layout(Launcher launcher) {
         FrameLayout.LayoutParams lp;
         boolean hasVerticalBarLayout = isVerticalBarLayout();
         final boolean isLayoutRtl = Utilities.isRtl(launcher.getResources());
 
         // Layout the search bar space
-        Rect searchBarBounds = getSearchBarBounds(isLayoutRtl);
-        View searchBar = launcher.getSearchDropTargetBar();
+        Point searchBarBounds = getSearchBarDimensForWidgetOpts();
+        View searchBar = launcher.getDropTargetBar();
         lp = (FrameLayout.LayoutParams) searchBar.getLayoutParams();
-        lp.width = searchBarBounds.width();
-        lp.height = searchBarBounds.height();
-        lp.topMargin = searchBarTopExtraPaddingPx;
+        lp.width = searchBarBounds.x;
+        lp.height = searchBarBounds.y;
+        lp.topMargin = edgeMarginPx;
         searchBar.setLayoutParams(lp);
 
-        // Layout the app info bar space
-        View appInfoBar = launcher.getAppInfoDropTargetBar();
-        lp = (FrameLayout.LayoutParams) appInfoBar.getLayoutParams();
-        lp.bottomMargin = hotseatBarHeightPx;
-        appInfoBar.setLayoutParams(lp);
-
         // Layout the workspace
         PagedView workspace = (PagedView) launcher.findViewById(R.id.workspace);
         lp = (FrameLayout.LayoutParams) workspace.getLayoutParams();
         lp.gravity = Gravity.CENTER;
-        Rect padding = getWorkspacePadding(isLayoutRtl);
+        Rect padding = getWorkspacePadding();
         workspace.setLayoutParams(lp);
         workspace.setPadding(padding.left, padding.top, padding.right, padding.bottom);
-        workspace.setPageSpacing(getWorkspacePageSpacing(isLayoutRtl));
+        workspace.setPageSpacing(getWorkspacePageSpacing());
 
         // Layout the hotseat
         View hotseat = launcher.findViewById(R.id.hotseat);
@@ -503,7 +431,7 @@
             // Vertical hotseat -- The hotseat is fixed in the layout to be on the right of the
             //                     screen regardless of RTL
             lp.gravity = Gravity.RIGHT;
-            lp.width = normalHotseatBarHeightPx;
+            lp.width = hotseatBarHeightPx;
             lp.height = LayoutParams.MATCH_PARENT;
             hotseat.findViewById(R.id.layout).setPadding(0, 2 * edgeMarginPx, 0, 2 * edgeMarginPx);
         } else if (isTablet) {
diff --git a/src/com/android/launcher3/DropTargetBar.java b/src/com/android/launcher3/DropTargetBar.java
new file mode 100644
index 0000000..5966af5
--- /dev/null
+++ b/src/com/android/launcher3/DropTargetBar.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import android.animation.TimeInterpolator;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewDebug;
+import android.view.ViewPropertyAnimator;
+import android.view.accessibility.AccessibilityManager;
+import android.view.animation.AccelerateInterpolator;
+import android.widget.LinearLayout;
+
+import com.android.launcher3.dragndrop.DragController;
+
+/*
+ * 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();
+
+    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);
+        }
+    };
+
+    @ViewDebug.ExportedProperty(category = "launcher")
+    protected boolean mDeferOnDragEnd;
+
+    @ViewDebug.ExportedProperty(category = "launcher")
+    protected boolean mVisible = false;
+
+    private ViewPropertyAnimator mCurrentAnimation;
+
+    // Drop targets
+    private ButtonDropTarget mDeleteDropTarget;
+    private ButtonDropTarget mAppInfoDropTarget;
+    private ButtonDropTarget mUninstallDropTarget;
+
+    public DropTargetBar(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public DropTargetBar(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        // Get the individual components
+        mDeleteDropTarget = (ButtonDropTarget) findViewById(R.id.delete_target_text);
+        mAppInfoDropTarget = (ButtonDropTarget) findViewById(R.id.info_target_text);
+        mUninstallDropTarget = (ButtonDropTarget) findViewById(R.id.uninstall_target_text);
+
+        mDeleteDropTarget.setDropTargetBar(this);
+        mAppInfoDropTarget.setDropTargetBar(this);
+        mUninstallDropTarget.setDropTargetBar(this);
+
+        // Initialize with hidden state
+        setAlpha(0f);
+    }
+
+    public void setup(DragController dragController) {
+        dragController.addDragListener(this);
+        dragController.setFlingToDeleteDropTarget(mDeleteDropTarget);
+
+        dragController.addDragListener(mDeleteDropTarget);
+        dragController.addDragListener(mAppInfoDropTarget);
+        dragController.addDragListener(mUninstallDropTarget);
+
+        dragController.addDropTarget(mDeleteDropTarget);
+        dragController.addDropTarget(mAppInfoDropTarget);
+        dragController.addDropTarget(mUninstallDropTarget);
+    }
+
+    private void animateToVisibility(boolean isVisible) {
+        if (mVisible != isVisible) {
+            mVisible = isVisible;
+
+            // Cancel any existing animation
+            if (mCurrentAnimation != null) {
+                mCurrentAnimation.cancel();
+                mCurrentAnimation = null;
+            }
+
+            float finalAlpha = mVisible ? 1 : 0;
+            if (Float.compare(getAlpha(), finalAlpha) != 0) {
+                setVisibility(View.VISIBLE);
+                mCurrentAnimation = animate().alpha(finalAlpha)
+                        .setInterpolator(DEFAULT_INTERPOLATOR)
+                        .setDuration(DEFAULT_DRAG_FADE_DURATION)
+                        .withEndAction(mFadeAnimationEndRunnable);
+            }
+
+        }
+    }
+
+    public void enableAccessibleDrag(boolean enable) {
+        mDeleteDropTarget.enableAccessibleDrag(enable);
+        mAppInfoDropTarget.enableAccessibleDrag(enable);
+        mUninstallDropTarget.enableAccessibleDrag(enable);
+    }
+
+    /*
+     * DragController.DragListener implementation
+     */
+    @Override
+    public void onDragStart(DragSource source, ItemInfo info, int dragAction) {
+        animateToVisibility(true);
+    }
+
+    /**
+     * This is called to defer hiding the delete drop target until the drop animation has completed,
+     * instead of hiding immediately when the drag has ended.
+     */
+    protected void deferOnDragEnd() {
+        mDeferOnDragEnd = true;
+    }
+
+    @Override
+    public void onDragEnd() {
+        if (!mDeferOnDragEnd) {
+            animateToVisibility(false);
+        } else {
+            mDeferOnDragEnd = false;
+        }
+    }
+}
diff --git a/src/com/android/launcher3/FocusHelper.java b/src/com/android/launcher3/FocusHelper.java
index f99c08a..0b9e4ac 100644
--- a/src/com/android/launcher3/FocusHelper.java
+++ b/src/com/android/launcher3/FocusHelper.java
@@ -356,7 +356,7 @@
         CellLayout iconLayout = (CellLayout) parent.getParent();
         final Workspace workspace = (Workspace) iconLayout.getParent();
         final ViewGroup dragLayer = (ViewGroup) workspace.getParent();
-        final ViewGroup tabs = (ViewGroup) dragLayer.findViewById(R.id.search_drop_target_bar);
+        final ViewGroup tabs = (ViewGroup) dragLayer.findViewById(R.id.drop_target_bar);
         final Hotseat hotseat = (Hotseat) dragLayer.findViewById(R.id.hotseat);
 
         final ItemInfo itemInfo = (ItemInfo) v.getTag();
diff --git a/src/com/android/launcher3/InsettableFrameLayout.java b/src/com/android/launcher3/InsettableFrameLayout.java
index f4bfa45..61edc0f 100644
--- a/src/com/android/launcher3/InsettableFrameLayout.java
+++ b/src/com/android/launcher3/InsettableFrameLayout.java
@@ -9,6 +9,9 @@
 import android.view.ViewGroup;
 import android.widget.FrameLayout;
 
+import com.android.launcher3.allapps.AllAppsContainerView;
+import com.android.launcher3.config.FeatureFlags;
+
 public class InsettableFrameLayout extends FrameLayout implements
     ViewGroup.OnHierarchyChangeListener, Insettable {
 
@@ -31,6 +34,9 @@
             lp.rightMargin += (newInsets.right - oldInsets.right);
             lp.bottomMargin += (newInsets.bottom - oldInsets.bottom);
         }
+        if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && child instanceof AllAppsContainerView) {
+            lp.setMargins(0, 0, 0, lp.bottomMargin);
+        }
         child.setLayoutParams(lp);
     }
 
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index ca31ea5..e1d292c 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -92,6 +92,7 @@
 
 import com.android.launcher3.DropTarget.DragObject;
 import com.android.launcher3.allapps.AllAppsContainerView;
+import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.allapps.DefaultAppSearchController;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 import com.android.launcher3.compat.LauncherActivityInfoCompat;
@@ -251,11 +252,11 @@
     private View mAllAppsButton;
     private View mWidgetsButton;
 
-    private SearchDropTargetBar mSearchDropTargetBar;
-    private AppInfoDropTargetBar mAppInfoDropTargetBar;
+    private DropTargetBar mDropTargetBar;
 
     // Main container view for the all apps screen.
     @Thunk AllAppsContainerView mAppsView;
+    AllAppsTransitionController mAllAppsController;
 
     // Main container view and the model for the widget tray screen.
     @Thunk WidgetsContainerView mWidgetsView;
@@ -413,7 +414,8 @@
         mIconCache = app.getIconCache();
 
         mDragController = new DragController(this);
-        mStateTransitionAnimation = new LauncherStateTransitionAnimation(this);
+        mAllAppsController = new AllAppsTransitionController(this);
+        mStateTransitionAnimation = new LauncherStateTransitionAnimation(this, mAllAppsController);
 
         mAppWidgetManager = AppWidgetManagerCompat.getInstance(this);
 
@@ -432,8 +434,6 @@
 
         setContentView(R.layout.launcher);
 
-        app.getInvariantDeviceProfile().landscapeProfile.setSearchBarHeight(getSearchBarHeight());
-        app.getInvariantDeviceProfile().portraitProfile.setSearchBarHeight(getSearchBarHeight());
         setupViews();
         mDeviceProfile.layout(this);
         mExtractedColors = new ExtractedColors();
@@ -1339,8 +1339,6 @@
      * Finds all the views we need and configure them properly.
      */
     private void setupViews() {
-        final DragController dragController = mDragController;
-
         mLauncherView = findViewById(R.id.launcher);
         mFocusHandler = (FocusIndicatorView) findViewById(R.id.focus_indicator);
         mDragLayer = (DragLayer) findViewById(R.id.drag_layer);
@@ -1353,7 +1351,8 @@
         mWorkspaceBackgroundDrawable = getResources().getDrawable(R.drawable.workspace_bg);
 
         // Setup the drag layer
-        mDragLayer.setup(this, dragController);
+
+        mDragLayer.setup(this, mDragController, mAllAppsController);
 
         // Setup the hotseat
         mHotseat = (Hotseat) findViewById(R.id.hotseat);
@@ -1367,16 +1366,12 @@
         // Setup the workspace
         mWorkspace.setHapticFeedbackEnabled(false);
         mWorkspace.setOnLongClickListener(this);
-        mWorkspace.setup(dragController);
+        mWorkspace.setup(mDragController);
         mWorkspace.bindAndInitFirstWorkspaceScreen(null /* recycled qsb */);
-        dragController.addDragListener(mWorkspace);
+        mDragController.addDragListener(mWorkspace);
 
         // Get the search/delete/uninstall bar
-        mSearchDropTargetBar = (SearchDropTargetBar)
-                mDragLayer.findViewById(R.id.search_drop_target_bar);
-        // Get the app info bar
-        mAppInfoDropTargetBar = (AppInfoDropTargetBar)
-                mDragLayer.findViewById(R.id.app_info_drop_target_bar);
+        mDropTargetBar = (DropTargetBar) mDragLayer.findViewById(R.id.drop_target_bar);
 
         // Setup Apps and Widgets
         mAppsView = (AllAppsContainerView) findViewById(R.id.apps_view);
@@ -1388,16 +1383,11 @@
         }
 
         // Setup the drag controller (drop targets have to be added in reverse order in priority)
-        dragController.setDragScoller(mWorkspace);
-        dragController.setScrollView(mDragLayer);
-        dragController.setMoveTarget(mWorkspace);
-        dragController.addDropTarget(mWorkspace);
-        if (mSearchDropTargetBar != null) {
-            mSearchDropTargetBar.setup(this, dragController);
-        }
-        if (mAppInfoDropTargetBar != null) {
-            mAppInfoDropTargetBar.setup(this, dragController);
-        }
+        mDragController.setDragScoller(mWorkspace);
+        mDragController.setScrollView(mDragLayer);
+        mDragController.setMoveTarget(mWorkspace);
+        mDragController.addDropTarget(mWorkspace);
+        mDropTargetBar.setup(mDragController);
 
         if (TestingUtils.MEMORY_DUMP_ENABLED) {
             TestingUtils.addWeightWatcher(this);
@@ -1806,12 +1796,8 @@
         return mOverviewPanel;
     }
 
-    public SearchDropTargetBar getSearchDropTargetBar() {
-        return mSearchDropTargetBar;
-    }
-
-    public AppInfoDropTargetBar getAppInfoDropTargetBar() {
-        return mAppInfoDropTargetBar;
+    public DropTargetBar getDropTargetBar() {
+        return mDropTargetBar;
     }
 
     public LauncherAppWidgetHost getAppWidgetHost() {
@@ -1940,6 +1926,7 @@
         if (mWorkspace.getChildCount() > 0) {
             outState.putInt(RUNTIME_STATE_CURRENT_SCREEN,
                     mWorkspace.getCurrentPageOffsetFromCustomContent());
+
         }
         super.onSaveInstanceState(outState);
 
@@ -3163,6 +3150,8 @@
                     mWorkspace.startReordering(v);
                 } else {
                     showOverviewMode(true);
+                    mHotseat.requestDisallowInterceptTouchEvent(true);
+
                 }
             } else {
                 final boolean isAllAppsButton = inHotseat && isAllAppsButtonRank(
@@ -3318,7 +3307,7 @@
     /**
      * Shows the apps view.
      */
-    void showAppsView(boolean animated, boolean resetListToTop, boolean updatePredictedApps,
+    public void showAppsView(boolean animated, boolean resetListToTop, boolean updatePredictedApps,
             boolean focusSearchBar) {
         if (resetListToTop) {
             mAppsView.scrollToTop();
@@ -4034,11 +4023,6 @@
         return mDeviceProfile.isVerticalBarLayout();
     }
 
-    /** Returns the search bar bounds in pixels. */
-    protected Rect getSearchBarBounds() {
-        return mDeviceProfile.getSearchBarBounds(Utilities.isRtl(getResources()));
-    }
-
     public int getSearchBarHeight() {
         if (mLauncherCallbacks != null) {
             return mLauncherCallbacks.getSearchBarHeight();
diff --git a/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java b/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
index 28d8052..f245cd3 100644
--- a/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
+++ b/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
@@ -63,9 +63,8 @@
         LauncherAppState app = LauncherAppState.getInstance();
         InvariantDeviceProfile idp = app.getInvariantDeviceProfile();
 
-        // We only care out the cell size, which is independent of the the layout direction.
-        Rect paddingLand = idp.landscapeProfile.getWorkspacePadding(false /* isLayoutRtl */);
-        Rect paddingPort = idp.portraitProfile.getWorkspacePadding(false /* isLayoutRtl */);
+        Rect paddingLand = idp.landscapeProfile.getWorkspacePadding();
+        Rect paddingPort = idp.portraitProfile.getWorkspacePadding();
 
         // Always assume we're working with the smallest span to make sure we
         // reserve enough space in both orientations.
diff --git a/src/com/android/launcher3/LauncherStateTransitionAnimation.java b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
index 17a5424..ca09f31 100644
--- a/src/com/android/launcher3/LauncherStateTransitionAnimation.java
+++ b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
@@ -33,6 +33,8 @@
 import android.view.animation.DecelerateInterpolator;
 
 import com.android.launcher3.allapps.AllAppsContainerView;
+import com.android.launcher3.allapps.AllAppsTransitionController;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.util.UiThreadCircularReveal;
 import com.android.launcher3.widget.WidgetsContainerView;
@@ -82,6 +84,17 @@
  */
 public class LauncherStateTransitionAnimation {
 
+    /**
+     * animation used for all apps and widget tray when
+     *{@link FeatureFlags#LAUNCHER3_ALL_APPS_PULL_UP} is {@code false}
+     */
+    public static final int CIRCULAR_REVEAL = 0;
+    /**
+     * animation used for all apps and not widget tray when
+     *{@link FeatureFlags#LAUNCHER3_ALL_APPS_PULL_UP} is {@code true}
+     */
+    public static final int PULLUP = 1;
+
     private static final float FINAL_REVEAL_ALPHA_FOR_WIDGETS = 0.3f;
 
     /**
@@ -113,9 +126,11 @@
 
     @Thunk Launcher mLauncher;
     @Thunk AnimatorSet mCurrentAnimation;
+    AllAppsTransitionController mAllAppsController;
 
-    public LauncherStateTransitionAnimation(Launcher l) {
+    public LauncherStateTransitionAnimation(Launcher l, AllAppsTransitionController allAppsController) {
         mLauncher = l;
+        mAllAppsController = allAppsController;
     }
 
     /**
@@ -154,9 +169,13 @@
                 }
             }
         };
+        int animType = CIRCULAR_REVEAL;
+        if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
+            animType = PULLUP;
+        }
         // Only animate the search bar if animating from spring loaded mode back to all apps
         mCurrentAnimation = startAnimationToOverlay(fromWorkspaceState,
-                Workspace.State.NORMAL_HIDDEN, buttonView, toView, animated, cb);
+                Workspace.State.NORMAL_HIDDEN, buttonView, toView, animated, animType, cb);
     }
 
     /**
@@ -167,7 +186,7 @@
         final WidgetsContainerView toView = mLauncher.getWidgetsView();
         final View buttonView = mLauncher.getWidgetsButton();
         mCurrentAnimation = startAnimationToOverlay(fromWorkspaceState,
-                Workspace.State.OVERVIEW_HIDDEN, buttonView, toView, animated,
+                Workspace.State.OVERVIEW_HIDDEN, buttonView, toView, animated, CIRCULAR_REVEAL,
                 new PrivateTransitionCallbacks(FINAL_REVEAL_ALPHA_FOR_WIDGETS){
                     @Override
                     void onTransitionComplete() {
@@ -189,8 +208,12 @@
         }
 
         if (fromState == Launcher.State.APPS || fromState == Launcher.State.APPS_SPRING_LOADED) {
+            int animType = CIRCULAR_REVEAL;
+            if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
+                animType = PULLUP;
+            }
             startAnimationToWorkspaceFromAllApps(fromWorkspaceState, toWorkspaceState,
-                    animated, onCompleteRunnable);
+                    animated, animType, onCompleteRunnable);
         } else if (fromState == Launcher.State.WIDGETS ||
                 fromState == Launcher.State.WIDGETS_SPRING_LOADED) {
             startAnimationToWorkspaceFromWidgets(fromWorkspaceState, toWorkspaceState,
@@ -208,7 +231,7 @@
     private AnimatorSet startAnimationToOverlay(
             final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState,
             final View buttonView, final BaseContainerView toView,
-            final boolean animated, final PrivateTransitionCallbacks pCb) {
+            final boolean animated, int animType, final PrivateTransitionCallbacks pCb) {
         final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
         final Resources res = mLauncher.getResources();
         final boolean material = Utilities.ATLEAST_LOLLIPOP;
@@ -225,12 +248,35 @@
         // Cancel the current animation
         cancelAnimation();
 
-        playCommonTransitionAnimations(toWorkspaceState, fromView, toView,
-                animated, initialized, animation, revealDuration, layerViews);
-
+        if (!FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
+            playCommonTransitionAnimations(toWorkspaceState, fromView, toView,
+                    animated, initialized, animation, revealDuration, layerViews);
+        }
         final View contentView = toView.getContentView();
 
-        if (animated && initialized) {
+        if (!animated || !initialized) {
+            toView.setTranslationX(0.0f);
+            toView.setTranslationY(0.0f);
+            toView.setScaleX(1.0f);
+            toView.setScaleY(1.0f);
+            toView.setAlpha(1.0f);
+            toView.setVisibility(View.VISIBLE);
+            toView.bringToFront();
+
+            // Show the content view
+            contentView.setVisibility(View.VISIBLE);
+
+            dispatchOnLauncherTransitionPrepare(fromView, animated, false);
+            dispatchOnLauncherTransitionStart(fromView, animated, false);
+            dispatchOnLauncherTransitionEnd(fromView, animated, false);
+            dispatchOnLauncherTransitionPrepare(toView, animated, false);
+            dispatchOnLauncherTransitionStart(toView, animated, false);
+            dispatchOnLauncherTransitionEnd(toView, animated, false);
+            pCb.onTransitionComplete();
+
+            return null;
+        }
+        if (animType == CIRCULAR_REVEAL) {
             // Setup the reveal view animation
             final View revealView = toView.getRevealView();
 
@@ -366,27 +412,40 @@
             toView.post(startAnimRunnable);
 
             return animation;
-        } else {
-            toView.setTranslationX(0.0f);
-            toView.setTranslationY(0.0f);
-            toView.setScaleX(1.0f);
-            toView.setScaleY(1.0f);
-            toView.setVisibility(View.VISIBLE);
-            toView.bringToFront();
+        } else if (animType == PULLUP) {
+            animation.addListener(new AnimatorListenerAdapter() {
+                  @Override
+                  public void onAnimationEnd(Animator animation) {
+                      dispatchOnLauncherTransitionEnd(fromView, animated, false);
+                      dispatchOnLauncherTransitionEnd(toView, animated, false);
+                      cleanupAnimation();
+                      pCb.onTransitionComplete();
+                  }
 
-            // Show the content view
-            contentView.setVisibility(View.VISIBLE);
+            });
+            mAllAppsController.animateToAllApps(animation);
 
             dispatchOnLauncherTransitionPrepare(fromView, animated, false);
-            dispatchOnLauncherTransitionStart(fromView, animated, false);
-            dispatchOnLauncherTransitionEnd(fromView, animated, false);
             dispatchOnLauncherTransitionPrepare(toView, animated, false);
-            dispatchOnLauncherTransitionStart(toView, animated, false);
-            dispatchOnLauncherTransitionEnd(toView, animated, false);
-            pCb.onTransitionComplete();
 
-            return null;
+            final AnimatorSet stateAnimation = animation;
+            final Runnable startAnimRunnable = new Runnable() {
+                public void run() {
+                    // Check that mCurrentAnimation hasn't changed while
+                    // we waited for a layout/draw pass
+                    if (mCurrentAnimation != stateAnimation)
+                        return;
+                    dispatchOnLauncherTransitionStart(fromView, animated, false);
+                    dispatchOnLauncherTransitionStart(toView, animated, false);
+
+                    toView.requestFocus();
+                    stateAnimation.start();
+                }
+            };
+            toView.post(startAnimRunnable);
+            return animation;
         }
+        return null;
     }
 
     /**
@@ -401,12 +460,6 @@
         Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState,
                 animated, layerViews);
 
-        // Animate the search bar
-        final SearchDropTargetBar.State toSearchBarState =
-                toWorkspaceState.searchDropTargetBarState;
-        mLauncher.getSearchDropTargetBar().animateToState(toSearchBarState,
-                animated ? revealDuration : 0, animation);
-
         if (animated && initialized) {
             // Play the workspace animation
             if (workspaceAnim != null) {
@@ -439,7 +492,7 @@
      * Starts an animation to the workspace from the apps view.
      */
     private void startAnimationToWorkspaceFromAllApps(final Workspace.State fromWorkspaceState,
-            final Workspace.State toWorkspaceState, final boolean animated, 
+            final Workspace.State toWorkspaceState, final boolean animated, int type,
             final Runnable onCompleteRunnable) {
         AllAppsContainerView appsView = mLauncher.getAppsView();
         // No alpha anim from all apps
@@ -476,7 +529,7 @@
         // Only animate the search bar if animating to spring loaded mode from all apps
         mCurrentAnimation = startAnimationToWorkspaceFromOverlay(fromWorkspaceState, toWorkspaceState,
                 mLauncher.getAllAppsButton(), appsView,
-                animated, onCompleteRunnable, cb);
+                animated, type, onCompleteRunnable, cb);
     }
 
     /**
@@ -506,7 +559,7 @@
         mCurrentAnimation = startAnimationToWorkspaceFromOverlay(
                 fromWorkspaceState, toWorkspaceState,
                 mLauncher.getWidgetsButton(), widgetsView,
-                animated, onCompleteRunnable, cb);
+                animated, CIRCULAR_REVEAL, onCompleteRunnable, cb);
     }
 
     /**
@@ -598,7 +651,7 @@
     private AnimatorSet startAnimationToWorkspaceFromOverlay(
             final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState,
             final View buttonView, final BaseContainerView fromView,
-            final boolean animated, final Runnable onCompleteRunnable,
+            final boolean animated, int animType, final Runnable onCompleteRunnable,
             final PrivateTransitionCallbacks pCb) {
         final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
         final Resources res = mLauncher.getResources();
@@ -619,10 +672,31 @@
 
         boolean multiplePagesVisible = toWorkspaceState.hasMultipleVisiblePages;
 
-        playCommonTransitionAnimations(toWorkspaceState, fromView, toView,
-                animated, initialized, animation, revealDuration, layerViews);
+        if (!FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
+            playCommonTransitionAnimations(toWorkspaceState, fromView, toView,
+                    animated, initialized, animation, revealDuration, layerViews);
+        }
+        if (!animated || !initialized) {
+            if (!FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
+                mAllAppsController.finishPullDown();
+            }
+            fromView.setVisibility(View.GONE);
+            dispatchOnLauncherTransitionPrepare(fromView, animated, multiplePagesVisible);
+            dispatchOnLauncherTransitionStart(fromView, animated, true);
+            dispatchOnLauncherTransitionEnd(fromView, animated, true);
+            dispatchOnLauncherTransitionPrepare(toView, animated, multiplePagesVisible);
+            dispatchOnLauncherTransitionStart(toView, animated, true);
+            dispatchOnLauncherTransitionEnd(toView, animated, true);
+            pCb.onTransitionComplete();
 
-        if (animated && initialized) {
+            // Run any queued runnables
+            if (onCompleteRunnable != null) {
+                onCompleteRunnable.run();
+            }
+
+            return null;
+        }
+        if (animType == CIRCULAR_REVEAL) {
             final View revealView = fromView.getRevealView();
             final View contentView = fromView.getContentView();
 
@@ -791,23 +865,59 @@
             fromView.post(startAnimRunnable);
 
             return animation;
-        } else /* if (!(animated && initialized)) */ {
-            fromView.setVisibility(View.GONE);
-            dispatchOnLauncherTransitionPrepare(fromView, animated, multiplePagesVisible);
-            dispatchOnLauncherTransitionStart(fromView, animated, true);
-            dispatchOnLauncherTransitionEnd(fromView, animated, true);
-            dispatchOnLauncherTransitionPrepare(toView, animated, multiplePagesVisible);
-            dispatchOnLauncherTransitionStart(toView, animated, true);
-            dispatchOnLauncherTransitionEnd(toView, animated, true);
-            pCb.onTransitionComplete();
+        } else if (animType == PULLUP) {
+            animation.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    dispatchOnLauncherTransitionEnd(fromView, animated, false);
+                    dispatchOnLauncherTransitionEnd(toView, animated, false);
+                    cleanupAnimation();
+                    pCb.onTransitionComplete();
+                }
 
-            // Run any queued runnables
-            if (onCompleteRunnable != null) {
-                onCompleteRunnable.run();
-            }
+            });
+            mAllAppsController.animateToWorkspace(animation);
 
-            return null;
+            // Dispatch the prepare transition signal
+            dispatchOnLauncherTransitionPrepare(fromView, animated, false);
+            dispatchOnLauncherTransitionPrepare(toView, animated, false);
+
+            animation.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    dispatchOnLauncherTransitionEnd(fromView, animated, true);
+                    dispatchOnLauncherTransitionEnd(toView, animated, true);
+
+                    // Run any queued runnables
+                    if (onCompleteRunnable != null) {
+                        onCompleteRunnable.run();
+                    }
+
+                    // This can hold unnecessary references to views.
+                    cleanupAnimation();
+                    pCb.onTransitionComplete();
+                }
+            });
+
+            final AnimatorSet stateAnimation = animation;
+            final Runnable startAnimRunnable = new Runnable() {
+                public void run() {
+                    // Check that mCurrentAnimation hasn't changed while
+                    // we waited for a layout/draw pass
+                    if (mCurrentAnimation != stateAnimation)
+                        return;
+                    dispatchOnLauncherTransitionStart(fromView, animated, false);
+                    dispatchOnLauncherTransitionStart(toView, animated, false);
+
+                    // Focus the new view
+                    toView.requestFocus();
+                    stateAnimation.start();
+                }
+            };
+            fromView.post(startAnimRunnable);
+            return animation;
         }
+        return null;
     }
 
     /**
diff --git a/src/com/android/launcher3/PinchToOverviewListener.java b/src/com/android/launcher3/PinchToOverviewListener.java
index 0c8568e..6ee96fc 100644
--- a/src/com/android/launcher3/PinchToOverviewListener.java
+++ b/src/com/android/launcher3/PinchToOverviewListener.java
@@ -21,6 +21,8 @@
 import android.view.MotionEvent;
 import android.view.ScaleGestureDetector;
 
+import com.android.launcher3.util.TouchController;
+
 /**
  * Detects pinches and animates the Workspace to/from overview mode.
  *
@@ -30,7 +32,8 @@
  * @see PinchThresholdManager
  * @see PinchAnimationManager
  */
-public class PinchToOverviewListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
+public class PinchToOverviewListener extends ScaleGestureDetector.SimpleOnScaleGestureListener
+        implements TouchController {
     private static final float OVERVIEW_PROGRESS = 0f;
     private static final float WORKSPACE_PROGRESS = 1f;
     /**
@@ -63,15 +66,16 @@
         return mPinchStarted;
     }
 
-    public void onTouchEvent(MotionEvent ev) {
+    public boolean onTouchEvent(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 {
-                mPinchDetector.onTouchEvent(ev);
+                return mPinchDetector.onTouchEvent(ev);
             }
         }
+        return false;
     }
 
     @Override
diff --git a/src/com/android/launcher3/QsbContainerView.java b/src/com/android/launcher3/QsbContainerView.java
index 0a112d2..f931aba 100644
--- a/src/com/android/launcher3/QsbContainerView.java
+++ b/src/com/android/launcher3/QsbContainerView.java
@@ -18,7 +18,6 @@
 
 import android.app.Activity;
 import android.app.Fragment;
-import android.app.FragmentManager;
 import android.app.SearchManager;
 import android.appwidget.AppWidgetManager;
 import android.appwidget.AppWidgetProviderInfo;
@@ -44,8 +43,6 @@
  */
 public class QsbContainerView extends FrameLayout {
 
-    private boolean mBound;
-
     public QsbContainerView(Context context) {
         super(context);
     }
@@ -59,17 +56,6 @@
     }
 
     @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-
-        if (!mBound) {
-            FragmentManager fm = ((Launcher) getContext()).getFragmentManager();
-            fm.beginTransaction().add(R.id.qsb_container, new QsbFragment()).commit();
-            mBound = true;
-        }
-    }
-
-    @Override
     public void setPadding(int left, int top, int right, int bottom) {
         super.setPadding(0, 0, 0, 0);
     }
@@ -103,6 +89,8 @@
             getContext().registerReceiver(mRebindReceiver, filter);
         }
 
+        private FrameLayout mWrapper;
+
         @Override
         public View onCreateView(
                 LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
@@ -110,7 +98,12 @@
             if (savedInstanceState != null) {
                 sSavedWidgetId = savedInstanceState.getInt(QSB_WIDGET_ID, -1);
             }
+            mWrapper = new FrameLayout(getContext());
+            mWrapper.addView(createQsb(inflater, mWrapper));
+            return mWrapper;
+        }
 
+        private View createQsb(LayoutInflater inflater, ViewGroup container) {
             Launcher launcher = (Launcher) getActivity();
             mWidgetInfo = getSearchWidgetProvider(launcher);
             if (mWidgetInfo == null) {
@@ -222,10 +215,9 @@
         }
 
         private void rebindFragment() {
-            if (getActivity() != null) {
-                // Recreate the fragment. This will cause the qsb to be inflated again.
-                getActivity().getFragmentManager().beginTransaction()
-                        .replace(R.id.qsb_container, new QsbFragment()).commit();
+            if (mWrapper != null && getActivity() != null) {
+                mWrapper.removeAllViews();
+                mWrapper.addView(createQsb(getActivity().getLayoutInflater(), mWrapper));
             }
         }
 
diff --git a/src/com/android/launcher3/SearchDropTargetBar.java b/src/com/android/launcher3/SearchDropTargetBar.java
deleted file mode 100644
index e43e96c..0000000
--- a/src/com/android/launcher3/SearchDropTargetBar.java
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3;
-
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.animation.TimeInterpolator;
-import android.content.Context;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.ViewDebug;
-import android.view.animation.DecelerateInterpolator;
-
-import com.android.launcher3.dragndrop.DragController;
-import com.android.launcher3.util.Thunk;
-
-/*
- * This bar will manage the transition between the QSB search bar and the delete/uninstall drop
- * targets so that each of the individual ButtonDropTargets don't have to.
- */
-public class SearchDropTargetBar extends BaseDropTargetBar {
-
-    /** The different states that the search bar space can be in. */
-    public enum State {
-        INVISIBLE             (0f),
-        DROP_TARGET           (1f);
-
-        private final float mDropTargetBarAlpha;
-
-        State(float dtbAlpha) {
-            mDropTargetBarAlpha = dtbAlpha;
-        }
-    }
-
-
-    @ViewDebug.ExportedProperty(category = "launcher")
-    private State mState = State.INVISIBLE;
-
-    // Drop targets
-    private ButtonDropTarget mDeleteDropTarget;
-    private ButtonDropTarget mUninstallDropTarget;
-
-    public SearchDropTargetBar(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public SearchDropTargetBar(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-
-        // Get the individual components
-        mDeleteDropTarget = (ButtonDropTarget) mDropTargetBar.findViewById(R.id.delete_target_text);
-        mUninstallDropTarget = (ButtonDropTarget) mDropTargetBar
-                .findViewById(R.id.uninstall_target_text);
-
-        mDeleteDropTarget.setDropTargetBar(this);
-        mUninstallDropTarget.setDropTargetBar(this);
-    }
-
-    @Override
-    public void setup(Launcher launcher, DragController dragController) {
-        dragController.addDragListener(this);
-        dragController.setFlingToDeleteDropTarget(mDeleteDropTarget);
-
-        dragController.addDragListener(mDeleteDropTarget);
-        dragController.addDragListener(mUninstallDropTarget);
-
-        dragController.addDropTarget(mDeleteDropTarget);
-        dragController.addDropTarget(mUninstallDropTarget);
-
-        mDeleteDropTarget.setLauncher(launcher);
-        mUninstallDropTarget.setLauncher(launcher);
-    }
-
-    @Override
-    public void showDropTargets() {
-        animateToState(State.DROP_TARGET, DEFAULT_DRAG_FADE_DURATION);
-    }
-
-    @Override
-    public void hideDropTargets() {
-        animateToState(State.INVISIBLE, DEFAULT_DRAG_FADE_DURATION);
-    }
-
-    /**
-     * Animates the current search bar state to a new state.  If the {@param duration} is 0, then
-     * the state is applied immediately.
-     */
-    public void animateToState(State newState, int duration) {
-        animateToState(newState, duration, null);
-    }
-
-    public void animateToState(State newState, int duration, AnimatorSet animation) {
-        if (mState != newState) {
-            mState = newState;
-
-            resetAnimation(duration);
-            if (duration > 0) {
-                animateAlpha(mDropTargetBar, mState.mDropTargetBarAlpha, DEFAULT_INTERPOLATOR);
-            } else {
-                mDropTargetBar.setAlpha(mState.mDropTargetBarAlpha);
-                AlphaUpdateListener.updateVisibility(mDropTargetBar, mAccessibilityEnabled);
-            }
-
-            // Start the final animation
-            if (duration > 0) {
-                if (animation != null) {
-                    animation.play(mCurrentAnimation);
-                } else {
-                    mCurrentAnimation.start();
-                }
-            }
-        }
-    }
-
-    @Override
-    public void enableAccessibleDrag(boolean enable) {
-        mDeleteDropTarget.enableAccessibleDrag(enable);
-        mUninstallDropTarget.enableAccessibleDrag(enable);
-    }
-}
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index faaca72..bf8e314 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -176,26 +176,22 @@
     private Matrix mTempMatrix = new Matrix();
 
     private SpringLoadedDragController mSpringLoadedDragController;
-    private float mSpringLoadedShrinkFactor;
     private float mOverviewModeShrinkFactor;
 
     // State variable that indicates whether the pages are small (ie when you're
     // in all apps or customize mode)
 
     enum State {
-        NORMAL          (SearchDropTargetBar.State.INVISIBLE, false, false),
-        NORMAL_HIDDEN   (SearchDropTargetBar.State.INVISIBLE, false, false),
-        SPRING_LOADED   (SearchDropTargetBar.State.DROP_TARGET, false, true),
-        OVERVIEW        (SearchDropTargetBar.State.INVISIBLE, true, true),
-        OVERVIEW_HIDDEN (SearchDropTargetBar.State.INVISIBLE, true, false);
+        NORMAL          (false, false),
+        NORMAL_HIDDEN   (false, false),
+        SPRING_LOADED   (false, true),
+        OVERVIEW        (true, true),
+        OVERVIEW_HIDDEN (true, false);
 
-        public final SearchDropTargetBar.State searchDropTargetBarState;
         public final boolean shouldUpdateWidget;
         public final boolean hasMultipleVisiblePages;
 
-        State(SearchDropTargetBar.State searchBarState, boolean shouldUpdateWidget,
-                boolean hasMultipleVisiblePages) {
-            searchDropTargetBarState = searchBarState;
+        State(boolean shouldUpdateWidget, boolean hasMultipleVisiblePages) {
             this.shouldUpdateWidget = shouldUpdateWidget;
             this.hasMultipleVisiblePages = hasMultipleVisiblePages;
         }
@@ -316,9 +312,6 @@
         mWallpaperManager = WallpaperManager.getInstance(context);
 
         mWallpaperOffset = new WallpaperOffsetInterpolator(this);
-
-        mSpringLoadedShrinkFactor =
-                res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f;
         mOverviewModeShrinkFactor =
                 res.getInteger(R.integer.config_workspaceOverviewShrinkPercentage) / 100f;
 
@@ -347,6 +340,7 @@
     // estimate the size of a widget with spans hSpan, vSpan. return MAX_VALUE for each
     // dimension if unsuccessful
     public int[] estimateItemSize(ItemInfo itemInfo, boolean springLoaded) {
+        float shrinkFactor = mLauncher.getDeviceProfile().workspaceSpringLoadShrinkFactor;
         int[] size = new int[2];
         if (getChildCount() > 0) {
             // Use the first non-custom page to estimate the child position
@@ -355,8 +349,8 @@
             size[0] = r.width();
             size[1] = r.height();
             if (springLoaded) {
-                size[0] *= mSpringLoadedShrinkFactor;
-                size[1] *= mSpringLoadedShrinkFactor;
+                size[0] *= shrinkFactor;
+                size[1] *= shrinkFactor;
             }
             return size;
         } else {
@@ -1386,7 +1380,6 @@
         // TODO(adamcohen): figure out a final effect here. We may need to recommend
         // different effects based on device performance. On at least one relatively high-end
         // device I've tried, translating the launcher causes things to get quite laggy.
-        setTranslationAndAlpha(mLauncher.getSearchDropTargetBar(), transX, alpha);
         setTranslationAndAlpha(getPageIndicator(), transX, alpha);
         setTranslationAndAlpha(getChildAt(getCurrentPage()), transX, alpha);
         setTranslationAndAlpha(mLauncher.getHotseat(), transX, alpha);
@@ -1542,8 +1535,7 @@
             // Reset our click listener
             setOnClickListener(mLauncher);
         }
-        mLauncher.getSearchDropTargetBar().enableAccessibleDrag(enable);
-        mLauncher.getAppInfoDropTargetBar().enableAccessibleDrag(enable);
+        mLauncher.getDropTargetBar().enableAccessibleDrag(enable);
         mLauncher.getHotseat().getLayout()
             .enableAccessibleDrag(enable, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG);
     }
@@ -1944,7 +1936,7 @@
 
     int getOverviewModeTranslationY() {
         DeviceProfile grid = mLauncher.getDeviceProfile();
-        Rect workspacePadding = grid.getWorkspacePadding(Utilities.isRtl(getResources()));
+        Rect workspacePadding = grid.getWorkspacePadding();
         int overviewButtonBarHeight = grid.getOverviewModeButtonBarHeight();
 
         int scaledHeight = (int) (mOverviewModeShrinkFactor * getNormalChildHeight());
@@ -1957,15 +1949,26 @@
         return -workspaceOffsetTopEdge + overviewOffsetTopEdge;
     }
 
-    int getSpringLoadedTranslationY() {
+    float getSpringLoadedTranslationY() {
         DeviceProfile grid = mLauncher.getDeviceProfile();
-        Rect workspacePadding = grid.getWorkspacePadding(Utilities.isRtl(getResources()));
-        int scaledHeight = (int) (mSpringLoadedShrinkFactor * getNormalChildHeight());
-        int workspaceTop = mInsets.top + workspacePadding.top;
-        int workspaceBottom = getViewportHeight() - mInsets.bottom - workspacePadding.bottom;
-        int workspaceHeight = workspaceBottom - workspaceTop;
-        // Center the spring-loaded pages by translating it up by half of the reduced height.
-        return -(workspaceHeight - scaledHeight) / 2;
+        if (grid.isVerticalBarLayout() || getChildCount() == 0) {
+            return 0;
+        }
+        Rect workspacePadding = grid.getWorkspacePadding();
+
+        float scaledHeight = grid.workspaceSpringLoadShrinkFactor * getNormalChildHeight();
+        float shrunkTop = mInsets.top + grid.dropTargetBarSizePx;
+        float shrunkBottom = getViewportHeight() - mInsets.bottom
+                - workspacePadding.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() {
@@ -2056,7 +2059,12 @@
     @Override
     public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
         if (mPageIndicator instanceof PageIndicatorLine) {
-            ((PageIndicatorLine) mPageIndicator).setShouldAutoHide(mState != State.SPRING_LOADED);
+            boolean isNewStateSpringLoaded = mState == State.SPRING_LOADED;
+            ((PageIndicatorLine) mPageIndicator).setShouldAutoHide(!isNewStateSpringLoaded);
+            if (isNewStateSpringLoaded) {
+                // Show the page indicator at the same time as the rest of the transition.
+                showPageIndicatorAtCurrentScroll();
+            }
         }
     }
 
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index e268640..0f437c1 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -211,8 +211,7 @@
         mOverviewTransitionTime = res.getInteger(R.integer.config_overviewTransitionTime);
         mOverlayTransitionTime = res.getInteger(R.integer.config_overlayTransitionTime);
         mSpringLoadedTransitionTime = mOverlayTransitionTime / 2;
-        mSpringLoadedShrinkFactor =
-                res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100f;
+        mSpringLoadedShrinkFactor = mLauncher.getDeviceProfile().workspaceSpringLoadShrinkFactor;
         mOverviewModeShrinkFactor =
                 res.getInteger(R.integer.config_workspaceOverviewShrinkPercentage) / 100f;
         mWorkspaceScrimAlpha = res.getInteger(R.integer.config_workspaceScrimAlpha) / 100f;
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index c9bd02c..95ab286 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -20,16 +20,20 @@
 import android.content.res.Resources;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.support.v7.widget.LinearLayoutManager;
 import android.support.v7.widget.RecyclerView;
 import android.text.Selection;
 import android.text.SpannableStringBuilder;
 import android.text.method.TextKeyListener;
 import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Gravity;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewConfiguration;
+import android.widget.LinearLayout;
 
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.BaseContainerView;
@@ -40,6 +44,7 @@
 import com.android.launcher3.DragSource;
 import com.android.launcher3.DropTarget;
 import com.android.launcher3.ExtendedEditText;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
@@ -178,9 +183,13 @@
         mApps.setAdapter(mAdapter);
         mLayoutManager = mAdapter.getLayoutManager();
         mItemDecoration = mAdapter.getItemDecoration();
-        mRecyclerViewTopBottomPadding =
-                res.getDimensionPixelSize(R.dimen.all_apps_list_top_bottom_padding);
-
+        if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
+            mRecyclerViewTopBottomPadding = 0;
+            setPadding(0, 0, 0, 0);
+        } else {
+            mRecyclerViewTopBottomPadding =
+                    res.getDimensionPixelSize(R.dimen.all_apps_list_top_bottom_padding);
+        }
         mSearchQueryBuilder = new SpannableStringBuilder();
         Selection.setSelection(mSearchQueryBuilder, 0);
     }
@@ -220,6 +229,13 @@
         mApps.removeApps(apps);
     }
 
+    public void setSearchBarVisible(boolean visible) {
+        if (visible) {
+            mSearchBarController.setVisibility(View.VISIBLE);
+        } else {
+            mSearchBarController.setVisibility(View.INVISIBLE);
+        }
+    }
     /**
      * Sets the search bar that shows above the a-z list.
      */
@@ -239,6 +255,10 @@
         mAppsRecyclerView.scrollToTop();
     }
 
+    public boolean isScrollAtTop() {
+        return ((LinearLayoutManager) mAppsRecyclerView.getLayoutManager())
+                .findFirstVisibleItemPosition() == 1;
+    }
     /**
      * Focuses the search field and begins an app search.
      */
@@ -321,13 +341,33 @@
                 MeasureSpec.getSize(widthMeasureSpec) - mHorizontalPadding,
                 MeasureSpec.getSize(heightMeasureSpec));
 
+        DeviceProfile grid = mLauncher.getDeviceProfile();
+        int availableWidth = (!mContentBounds.isEmpty() ? mContentBounds.width() :
+                MeasureSpec.getSize(widthMeasureSpec))
+                - 2 * mAppsRecyclerView.getMaxScrollbarWidth();
+        if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
+            if (mNumAppsPerRow != grid.inv.numColumns ||
+                    mNumPredictedAppsPerRow != grid.inv.numColumns) {
+                mNumAppsPerRow = grid.inv.numColumns;
+                mNumPredictedAppsPerRow = grid.inv.numColumns;
+
+                mAppsRecyclerView.setNumAppsPerRow(grid, mNumAppsPerRow);
+                mAdapter.setNumAppsPerRow(mNumAppsPerRow);
+                mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow, new FullMergeAlgorithm());
+                if (mNumAppsPerRow > 0) {
+                    int iconSize = availableWidth / mNumAppsPerRow;
+                    int iconSpacing = (iconSize - grid.allAppsIconSizePx) / 2;
+                }
+            }
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+            return;
+        }
+
+        // --- remove START when {@code FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP} is enabled. ---
+
         // Update the number of items in the grid before we measure the view
         // TODO: mSectionNamesMargin is currently 0, but also account for it,
         // if it's enabled in the future.
-        int availableWidth = (!mContentBounds.isEmpty() ? mContentBounds.width() :
-                MeasureSpec.getSize(widthMeasureSpec))
-                    - 2 * mAppsRecyclerView.getMaxScrollbarWidth();
-        DeviceProfile grid = mLauncher.getDeviceProfile();
         grid.updateAppsViewNumCols(getResources(), availableWidth);
         if (mNumAppsPerRow != grid.allAppsNumCols ||
                 mNumPredictedAppsPerRow != grid.allAppsNumPredictiveCols) {
@@ -346,6 +386,8 @@
             mAdapter.setNumAppsPerRow(mNumAppsPerRow);
             mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow, mergeAlgorithm);
 
+            // TODO: should we not do all this complicated computation but just match the
+            // numAppsPerRow with the workspace?
             if (mNumAppsPerRow > 0) {
                 int iconSize = availableWidth / mNumAppsPerRow;
                 int iconSpacing = (iconSize - grid.allAppsIconSizePx) / 2;
@@ -353,6 +395,7 @@
             }
         }
 
+        // --- remove END when {@code FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP} is enabled. ---
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
     }
 
@@ -385,6 +428,24 @@
         MarginLayoutParams lp = (MarginLayoutParams) mSearchContainer.getLayoutParams();
         lp.leftMargin = bgPadding.left;
         lp.rightMargin = bgPadding.right;
+
+        if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
+            MarginLayoutParams mlp = (MarginLayoutParams) mAppsRecyclerView.getLayoutParams();
+
+            int navBarHeight = 84; /* replace with mInset.height() in dragLayer */
+            DeviceProfile grid = mLauncher.getDeviceProfile();
+            int height = navBarHeight + grid.hotseatCellHeightPx;
+
+            mlp.topMargin = height;
+            mAppsRecyclerView.setLayoutParams(mlp);
+
+            LinearLayout.LayoutParams llp =
+                    (LinearLayout.LayoutParams) mSearchInput.getLayoutParams();
+            llp.topMargin = navBarHeight;
+            mSearchInput.setLayoutParams(llp);
+
+            lp.height = height;
+        }
         mSearchContainer.setLayoutParams(lp);
     }
 
@@ -437,6 +498,7 @@
         // Return early if this is not initiated from a touch
         if (!v.isInTouchMode()) return false;
         // When we have exited all apps or are in transition, disregard long clicks
+
         if (!mLauncher.isAppsViewVisible() ||
                 mLauncher.getWorkspace().isSwitchingState()) return false;
         // Return if global dragging is not enabled
diff --git a/src/com/android/launcher3/allapps/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/AllAppsSearchBarController.java
index a4bea8d..ac35932 100644
--- a/src/com/android/launcher3/allapps/AllAppsSearchBarController.java
+++ b/src/com/android/launcher3/allapps/AllAppsSearchBarController.java
@@ -49,6 +49,9 @@
     protected DefaultAppSearchAlgorithm mSearchAlgorithm;
     protected InputMethodManager mInputMethodManager;
 
+    public void setVisibility(int visibility) {
+        mInput.setVisibility(visibility);
+    }
     /**
      * Sets the references to the apps model and the search result callback.
      */
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
new file mode 100644
index 0000000..1428c2f
--- /dev/null
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -0,0 +1,347 @@
+package com.android.launcher3.allapps;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.graphics.drawable.Drawable;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+
+import com.android.launcher3.Hotseat;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAnimUtils;
+import com.android.launcher3.util.TouchController;
+
+/**
+ * Handles AllApps view transition.
+ * 1) Slides all apps view using direct manipulation
+ * 2) When finger is released, animate to either top or bottom accordingly.
+ *
+ * Algorithm:
+ * If release velocity > THRES1, snap according to the direction of movement.
+ * 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, VerticalPullDetector.Listener {
+
+    private static final String TAG = "AllAppsTrans";
+    private static final boolean DBG = false;
+
+    private static final float ANIMATION_DURATION = 500;
+
+    private AllAppsContainerView mAppsView;
+    private Hotseat mHotseat;
+    private Drawable mHotseatBackground;
+    private float mHotseatAlpha;
+    private View mWorkspaceCurPage;
+
+    private final Launcher mLauncher;
+    private final VerticalPullDetector mDetector;
+
+    // Animation in this class is controlled by a single variable {@link mProgressTransY}.
+    // Visually, it represents top y coordinate of the all apps container. Using the
+    // {@link mTranslation} as the denominator, this fraction value ranges in [0, 1].
+    private float mProgressTransY;   // numerator
+    private float mTranslation = -1; // denominator
+
+    private float mAnimationDuration;
+    private float mCurY;
+
+    private AnimatorSet mCurrentAnimation;
+
+    public AllAppsTransitionController(Launcher launcher) {
+        mLauncher = launcher;
+        mDetector = new VerticalPullDetector(launcher);
+        mDetector.setListener(this);
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        init();
+        if (mLauncher.getWorkspace().isInOverviewMode() ||
+                mLauncher.isWidgetsViewVisible()) {
+            return false;
+        }
+        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+            mDetector.setAllAppsState(mLauncher.isAllAppsVisible(), mAppsView.isScrollAtTop());
+        }
+        mDetector.onTouchEvent(ev);
+        return mDetector.mScrolling;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        return mDetector.onTouchEvent(ev);
+    }
+
+    private void init() {
+        if (mAppsView != null) {
+            return;
+        }
+        mAppsView = mLauncher.getAppsView();
+        mHotseat = mLauncher.getHotseat();
+        mWorkspaceCurPage = mLauncher.getWorkspace().getChildAt(
+                mLauncher.getWorkspace().getCurrentPage());
+
+        if (mHotseatBackground == null) {
+            mHotseatBackground = mHotseat.getBackground();
+            mHotseatAlpha = mHotseatBackground.getAlpha() / 255f;
+        }
+    }
+
+    @Override
+    public void onScrollStart(boolean start) {
+        cancelAnimation();
+        mCurrentAnimation = LauncherAnimUtils.createAnimatorSet();
+        mCurY = mAppsView.getTranslationY();
+        preparePull(start);
+
+    }
+
+    /**
+     * @param start {@code true} if start of new drag.
+     */
+    public void preparePull(boolean start) {
+        if (start) {
+            if (!mLauncher.isAllAppsVisible()) {
+                mHotseat.setBackground(null);
+                mAppsView.setVisibility(View.VISIBLE);
+                mAppsView.getContentView().setVisibility(View.VISIBLE);
+                mAppsView.setAlpha(mHotseatAlpha);
+                mAppsView.setSearchBarVisible(false);
+
+                if (mTranslation < 0) {
+                    mTranslation = mHotseat.getTop();
+                    setProgress(mTranslation);
+                }
+            } else {
+                mLauncher.getWorkspace().setVisibility(View.VISIBLE);
+                mLauncher.getWorkspace().setAlpha(1f);
+                mLauncher.getWorkspace().onLauncherTransitionPrepare(mLauncher, false, false);
+                mWorkspaceCurPage.setVisibility(View.VISIBLE);
+                mAppsView.setSearchBarVisible(false);
+                setLightStatusBar(false);
+            }
+        }
+        mHotseat.setVisibility(View.VISIBLE);
+        mHotseat.bringToFront();
+    }
+
+    private void setLightStatusBar(boolean enable) {
+        int systemUiFlags = mLauncher.getWindow().getDecorView().getSystemUiVisibility();
+        if (enable) {
+            mLauncher.getWindow().getDecorView().setSystemUiVisibility(systemUiFlags
+                    | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
+
+        } else {
+            mLauncher.getWindow().getDecorView().setSystemUiVisibility(systemUiFlags
+                    & ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
+
+        }
+    }
+
+    private final Interpolator mAlphaInterpolator = new DecelerateInterpolator(.5f);
+
+    @Override
+    public boolean onScroll(float displacement, float velocity) {
+        if (mAppsView == null) {
+            return false;   // early termination.
+        }
+        if (0 <= mCurY + displacement && mCurY + displacement < mTranslation) {
+            setProgress(mCurY + displacement);
+        }
+        return true;
+    }
+
+    /**
+     * @param progress y value of the border between hotseat and all apps
+     */
+    public void setProgress(float progress) {
+        mProgressTransY = progress;
+        float alpha = calcAlphaAllApps(progress);
+        float workspaceHotseatAlpha = Math.max(mHotseatAlpha, 1 - alpha);
+        setTransAndAlpha(mAppsView, progress, Math.max(mHotseatAlpha, alpha));
+        setTransAndAlpha(mWorkspaceCurPage, -mTranslation + progress, workspaceHotseatAlpha);
+        setTransAndAlpha(mHotseat, -mTranslation + progress, workspaceHotseatAlpha);
+    }
+
+    public float getProgress() {
+        return mProgressTransY;
+    }
+
+    private float calcAlphaAllApps(float progress) {
+        return mAlphaInterpolator.getInterpolation((mTranslation - progress)/mTranslation);
+    }
+
+    private void setTransAndAlpha(View v, float transY, float alpha) {
+        if (v != null) {
+            v.setTranslationY(transY);
+            v.setAlpha(alpha);
+        }
+    }
+
+    @Override
+    public void onScrollEnd(float velocity, boolean fling) {
+        if (mAppsView == null) {
+            return; // early termination.
+        }
+
+        if (fling) {
+            if (velocity < 0) {
+                calculateDuration(velocity, mAppsView.getTranslationY());
+                showAppsView(); // Flinging in UP direction
+            } else {
+                calculateDuration(velocity, Math.abs(mTranslation - mAppsView.getTranslationY()));
+                showWorkspace(); // Flinging in DOWN direction
+            }
+            // snap to top or bottom using the release velocity
+        } else {
+            if (mAppsView.getTranslationY() > mTranslation / 2) {
+                calculateDuration(velocity, Math.abs(mTranslation - mAppsView.getTranslationY()));
+                showWorkspace(); // Released in the bottom half
+            } else {
+                calculateDuration(velocity, Math.abs(mAppsView.getTranslationY()));
+                showAppsView(); // Released in the top half
+            }
+        }
+    }
+
+    private void calculateDuration(float velocity, float disp) {
+        // TODO: make these values constants after tuning.
+        float velocityDivisor = Math.max(1, 0.75f * velocity);
+        float travelDistance = Math.max(0.2f, disp / mTranslation);
+        mAnimationDuration = Math.max(100, ANIMATION_DURATION / velocityDivisor * travelDistance);
+        if (true) { // MERONG
+            Log.d(TAG, String.format("calculateDuration=%f, v=%f, d=%f", mAnimationDuration, velocity, disp));
+        }
+    }
+
+    /**
+     * Depending on the current state of the launcher, either just
+     * 1) animate
+     * 2) animate and do all the state updates.
+     */
+    private void showAppsView() {
+        if (mLauncher.isAllAppsVisible()) {
+            animateToAllApps(mCurrentAnimation);
+            mCurrentAnimation.start();
+        } else {
+            mLauncher.showAppsView(true /* animated */, true /* resetListToTop */,
+                    true /* updatePredictedApps */, false /* focusSearchBar */);
+        }
+    }
+
+    /**
+     * Depending on the current state of the launcher, either just
+     * 1) animate
+     * 2) animate and do all the state updates.
+     */
+    private void showWorkspace() {
+        if (mLauncher.isAllAppsVisible()) {
+            mLauncher.showWorkspace(true /* animated */);
+        } else {
+            animateToWorkspace(mCurrentAnimation);
+            mCurrentAnimation.start();
+        }
+    }
+
+    public void animateToAllApps(AnimatorSet animationOut) {
+        if ((mAppsView = mLauncher.getAppsView()) == null || animationOut == null){
+            return;
+        }
+        if (!mDetector.mScrolling) {
+            preparePull(true);
+        }
+        mCurY = mAppsView.getTranslationY();
+        final float fromAllAppsTop = mAppsView.getTranslationY();
+        final float toAllAppsTop = 0;
+
+        ObjectAnimator driftAndAlpha = ObjectAnimator.ofFloat(this, "progress",
+                fromAllAppsTop, toAllAppsTop);
+        driftAndAlpha.setDuration((long) mAnimationDuration);
+        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 {
+                    finishPullUp();
+                    cleanUpAnimation();
+                    mDetector.finishedScrolling();
+                }
+            }});
+        mCurrentAnimation = animationOut;
+    }
+
+    private void finishPullUp() {
+        mAppsView.setSearchBarVisible(true);
+        mHotseat.setVisibility(View.INVISIBLE);
+        setProgress(0f);
+        setLightStatusBar(true);
+    }
+
+    public void animateToWorkspace(AnimatorSet animationOut) {
+        if ((mAppsView = mLauncher.getAppsView()) == null || animationOut == null){
+            return;
+        }
+        if(!mDetector.mScrolling) {
+            preparePull(true);
+        }
+        final float fromAllAppsTop = mAppsView.getTranslationY();
+        final float toAllAppsTop = mTranslation;
+
+        ObjectAnimator driftAndAlpha = ObjectAnimator.ofFloat(this, "progress",
+                fromAllAppsTop, toAllAppsTop);
+        driftAndAlpha.setDuration((long) mAnimationDuration);
+        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;
+    }
+
+    public void finishPullDown() {
+        mAppsView.setVisibility(View.INVISIBLE);
+        mHotseat.setBackground(mHotseatBackground);
+        mHotseat.setVisibility(View.VISIBLE);
+        setProgress(mTranslation);
+        setLightStatusBar(false);
+    }
+
+    private void cancelAnimation() {
+        if (mCurrentAnimation != null) {
+            mCurrentAnimation.cancel();
+            mCurrentAnimation = null;
+        }
+    }
+
+    private void cleanUpAnimation() {
+        mCurrentAnimation = null;
+    }
+}
diff --git a/src/com/android/launcher3/allapps/VerticalPullDetector.java b/src/com/android/launcher3/allapps/VerticalPullDetector.java
new file mode 100644
index 0000000..4cc921c
--- /dev/null
+++ b/src/com/android/launcher3/allapps/VerticalPullDetector.java
@@ -0,0 +1,196 @@
+package com.android.launcher3.allapps;
+
+import android.content.Context;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+/**
+ * One dimensional scroll gesture detector for all apps container pull up interaction.
+ */
+public class VerticalPullDetector {
+
+    private static final String TAG = "ScrollGesture";
+    private static final boolean DBG = false;
+
+    private float mTouchSlop;
+
+    private boolean mAllAppsVisible;
+    private boolean mAllAppsScrollAtTop;
+
+    /**
+     * The minimum release velocity in pixels per millisecond that triggers fling..
+     */
+    private static final float RELEASE_VELOCITY_PX_MS = 1.7f;
+
+    /**
+     * The time constant used to calculate dampening in the low-pass filter of scroll velocity.
+     * Cutoff frequency is set at 10 Hz.
+     */
+    public static final float SCROLL_VELOCITY_DAMPENING_RC = 1000f / (2f * (float) Math.PI * 10);
+
+    /* Scroll state, this is set to true during dragging and animation. */
+    boolean mScrolling;
+
+
+    float mDownY;
+    float mDownMillis;
+
+    float mLastY;
+    float mLastMillis;
+
+    float mVelocity;
+    float mLastDisplacement;
+    float mDisplacement;
+
+    /* scroll started during previous animation */
+    boolean mSubtractSlop = true;
+
+    /* Client of this gesture detector can register a callback. */
+    Listener mListener;
+
+    public void setListener(Listener l) {
+        mListener = l;
+    }
+
+    interface Listener{
+        /**
+         * @param start when should
+         */
+        void onScrollStart(boolean start);
+        boolean onScroll(float displacement, float velocity);
+        void onScrollEnd(float velocity, boolean fling);
+    }
+
+    public VerticalPullDetector(Context context) {
+        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+    }
+
+    public void setAllAppsState(boolean allAppsVisible, boolean scrollAtTop) {
+        mAllAppsVisible = allAppsVisible;
+        mAllAppsScrollAtTop = scrollAtTop;
+    }
+
+    private boolean shouldScrollStart() {
+        if (mAllAppsVisible && mDisplacement > mTouchSlop && mAllAppsScrollAtTop) {
+            return true;
+        }
+        if (!mAllAppsVisible && mDisplacement < -mTouchSlop) {
+            return true;
+        }
+        return false;
+    }
+
+    public boolean onTouchEvent(MotionEvent ev) {
+        switch (ev.getAction()) {
+            case MotionEvent.ACTION_DOWN:
+                mDownMillis = ev.getDownTime();
+                mDownY = ev.getY();
+                mLastDisplacement = 0;
+                mVelocity = 0;
+
+                if (mScrolling) {
+                    reportScrollStart(true /* recatch */);
+                }
+                break;
+            case MotionEvent.ACTION_MOVE:
+                mDisplacement = computeDisplacement(ev);
+                mVelocity = computeVelocity(ev, mVelocity);
+
+                if (!mScrolling && shouldScrollStart()) {
+                    mScrolling = true;
+                    reportScrollStart(false /* recatch */);
+                }
+                if (mScrolling && mListener != null) {
+                    reportScroll();
+                }
+                break;
+            case MotionEvent.ACTION_CANCEL:
+            case MotionEvent.ACTION_UP:
+                // These are synthetic events and there is no need to update internal values.
+                if (mScrolling && mListener != null) {
+                    reportScrollEnd();
+                }
+                break;
+            default:
+                //TODO: add multi finger tracking by tracking active pointer.
+                break;
+        }
+        // Do house keeping.
+        mLastDisplacement = mDisplacement;
+
+        mLastY = ev.getY();
+        mLastMillis = ev.getEventTime();
+
+        return true;
+    }
+
+    public void finishedScrolling() {
+        mScrolling = false;
+    }
+
+    private boolean reportScrollStart(boolean recatch) {
+        mListener.onScrollStart(!recatch);
+        if (DBG) {
+            Log.d(TAG, "onScrollStart recatch:" + recatch);
+        }
+        return true;
+    }
+
+    private boolean reportScroll() {
+        float delta = mDisplacement - mLastDisplacement;
+        if (delta != 0) {
+            if (DBG) {
+                Log.d(TAG, String.format("onScroll disp=%.1f, velocity=%.1f",
+                        mDisplacement, mVelocity));
+            }
+            return mListener.onScroll(mDisplacement - (mSubtractSlop? mTouchSlop : 0), mVelocity);
+        }
+        return true;
+    }
+
+    private void reportScrollEnd() {
+        if (DBG) {
+            Log.d(TAG, String.format("onScrolEnd disp=%.1f, velocity=%.1f",
+                    mDisplacement, mVelocity));
+        }
+        mListener.onScrollEnd(mVelocity, Math.abs(mVelocity) > RELEASE_VELOCITY_PX_MS);
+    }
+    /**
+     * Computes the damped velocity using the two motion events and the previous velocity.
+     */
+    private float computeVelocity(MotionEvent to, float previousVelocity) {
+        float delta = computeDelta(to);
+
+        float deltaTimeMillis = to.getEventTime() - mLastMillis;
+        float velocity = (deltaTimeMillis > 0) ? (delta / deltaTimeMillis) : 0;
+        if (Math.abs(previousVelocity) < 0.001f) {
+            return velocity;
+        }
+
+        float alpha = computeDampeningFactor(deltaTimeMillis);
+        return interpolate(previousVelocity, velocity, alpha);
+    }
+
+    private float computeDisplacement(MotionEvent to) {
+        return to.getY() - mDownY;
+    }
+
+    private float computeDelta(MotionEvent to) {
+        return to.getY() - mLastY;
+    }
+
+    /**
+     * Returns a time-dependent dampening factor using delta time.
+     */
+    private static float computeDampeningFactor(float deltaTime) {
+        return deltaTime / (SCROLL_VELOCITY_DAMPENING_RC + deltaTime);
+    }
+
+    /**
+     * Returns the linear interpolation between two values
+     */
+    private static float interpolate(float from, float to, float alpha) {
+        return (1.0f - alpha) * from + alpha * to;
+    }
+}
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index f4470f3..af5ff58 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -46,6 +46,7 @@
 import com.android.launcher3.Workspace;
 import com.android.launcher3.accessibility.DragViewStateAnnouncer;
 import com.android.launcher3.util.Thunk;
+import com.android.launcher3.util.TouchController;
 
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -53,7 +54,7 @@
 /**
  * Class for initiating a drag within a view or across multiple views.
  */
-public class DragController implements DragDriver.EventListener {
+public class DragController implements DragDriver.EventListener, TouchController {
     private static final String TAG = "Launcher.DragController";
 
     /** Indicates the drag is a move.  */
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index 9bb6cec..472da44 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -29,7 +29,6 @@
 import android.graphics.Region;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.view.DragEvent;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
@@ -43,6 +42,7 @@
 import android.widget.TextView;
 
 import com.android.launcher3.AppWidgetResizeFrame;
+import com.android.launcher3.BaseContainerView;
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.InsettableFrameLayout;
 import com.android.launcher3.ItemInfo;
@@ -51,15 +51,18 @@
 import com.android.launcher3.LauncherAppWidgetHostView;
 import com.android.launcher3.PinchToOverviewListener;
 import com.android.launcher3.R;
-import com.android.launcher3.SearchDropTargetBar;
+import com.android.launcher3.DropTargetBar;
 import com.android.launcher3.ShortcutAndWidgetContainer;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
+import com.android.launcher3.allapps.AllAppsContainerView;
+import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.util.Thunk;
+import com.android.launcher3.util.TouchController;
 
 import java.util.ArrayList;
 
@@ -117,6 +120,11 @@
 
     // Related to pinch-to-go-to-overview gesture.
     private PinchToOverviewListener mPinchListener = null;
+
+    // Handles all apps pull up interaction
+    private AllAppsTransitionController mAllAppsController;
+
+    private TouchController mActiveController;
     /**
      * Used to create a new DragLayer from XML.
      *
@@ -138,9 +146,11 @@
         mIsRtl = Utilities.isRtl(res);
     }
 
-    public void setup(Launcher launcher, DragController controller) {
+    public void setup(Launcher launcher, DragController dragController,
+            AllAppsTransitionController allAppsTransitionController) {
         mLauncher = launcher;
-        mDragController = controller;
+        mDragController = dragController;
+        mAllAppsController = allAppsTransitionController;
 
         boolean isAccessibilityEnabled = ((AccessibilityManager) mLauncher.getSystemService(
                 Context.ACCESSIBILITY_SERVICE)).isEnabled();
@@ -173,31 +183,17 @@
 
     private boolean isEventOverFolderTextRegion(Folder folder, MotionEvent ev) {
         getDescendantRectRelativeToSelf(folder.getEditTextRegion(), mHitRect);
-        if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) {
-            return true;
-        }
-        return false;
+        return mHitRect.contains((int) ev.getX(), (int) ev.getY());
     }
 
     private boolean isEventOverFolder(Folder folder, MotionEvent ev) {
         getDescendantRectRelativeToSelf(folder, mHitRect);
-        if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) {
-            return true;
-        }
-        return false;
+        return mHitRect.contains((int) ev.getX(), (int) ev.getY());
     }
 
     private boolean isEventOverDropTargetBar(MotionEvent ev) {
-        getDescendantRectRelativeToSelf(mLauncher.getSearchDropTargetBar(), mHitRect);
-        if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) {
-            return true;
-        }
-
-        getDescendantRectRelativeToSelf(mLauncher.getAppInfoDropTargetBar(), mHitRect);
-        if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) {
-            return true;
-        }
-        return false;
+        getDescendantRectRelativeToSelf(mLauncher.getDropTargetBar(), mHitRect);
+        return mHitRect.contains((int) ev.getX(), (int) ev.getY());
     }
 
     private boolean handleTouchDown(MotionEvent ev, boolean intercept) {
@@ -246,6 +242,7 @@
     public boolean onInterceptTouchEvent(MotionEvent ev) {
         int action = ev.getAction();
 
+
         if (action == MotionEvent.ACTION_DOWN) {
             if (handleTouchDown(ev, true)) {
                 return true;
@@ -258,11 +255,21 @@
         }
         clearAllResizeFrames();
 
-        if (mPinchListener != null && mPinchListener.onInterceptTouchEvent(ev)) {
-            // Stop listening for scrolling etc. (onTouchEvent() handles the rest of the pinch.)
+        if (mDragController.onInterceptTouchEvent(ev)) {
+            mActiveController = mDragController;
             return true;
         }
-        return mDragController.onInterceptTouchEvent(ev);
+        if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && mAllAppsController.onInterceptTouchEvent(ev)) {
+            mActiveController = mAllAppsController;
+            return true;
+        }
+
+        if (mPinchListener != null && mPinchListener.onInterceptTouchEvent(ev)) {
+            // Stop listening for scrolling etc. (onTouchEvent() handles the rest of the pinch.)
+            mActiveController = mPinchListener;
+            return true;
+        }
+        return false;
     }
 
     @Override
@@ -334,7 +341,7 @@
                 return super.onRequestSendAccessibilityEvent(child, event);
             }
 
-            if (isInAccessibleDrag() && child instanceof SearchDropTargetBar) {
+            if (isInAccessibleDrag() && child instanceof DropTargetBar) {
                 return super.onRequestSendAccessibilityEvent(child, event);
             }
             // Skip propagating onRequestSendAccessibilityEvent all for other children
@@ -352,8 +359,7 @@
             childrenForAccessibility.add(currentFolder);
 
             if (isInAccessibleDrag()) {
-                childrenForAccessibility.add(mLauncher.getSearchDropTargetBar());
-                childrenForAccessibility.add(mLauncher.getAppInfoDropTargetBar());
+                childrenForAccessibility.add(mLauncher.getDropTargetBar());
             }
         } else {
             super.addChildrenForAccessibility(childrenForAccessibility);
@@ -375,11 +381,6 @@
         int x = (int) ev.getX();
         int y = (int) ev.getY();
 
-        // This is only reached if a pinch was started from onInterceptTouchEvent();
-        // this continues sending events for it.
-        if (mPinchListener != null) {
-            mPinchListener.onTouchEvent(ev);
-        }
 
         if (action == MotionEvent.ACTION_DOWN) {
             if (handleTouchDown(ev, false)) {
@@ -406,7 +407,10 @@
             }
         }
         if (handled) return true;
-        return mDragController.onTouchEvent(ev);
+        if (mActiveController != null) {
+            return mActiveController.onTouchEvent(ev);
+        }
+        return false;
     }
 
     @Override
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 6df296e..af93707 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -318,7 +318,7 @@
 
     @Override
     public void enableAccessibleDrag(boolean enable) {
-        mLauncher.getSearchDropTargetBar().enableAccessibleDrag(enable);
+        mLauncher.getDropTargetBar().enableAccessibleDrag(enable);
         for (int i = 0; i < mContent.getChildCount(); i++) {
             mContent.getPageAt(i).enableAccessibleDrag(enable, CellLayout.FOLDER_ACCESSIBILITY_DRAG);
         }
@@ -1054,7 +1054,7 @@
         int top = Math.min(Math.max(sTempRect.top, centeredTop),
                 sTempRect.top + sTempRect.height() - height);
 
-        int distFromEdgeOfScreen = grid.getWorkspacePadding(isLayoutRtl()).left + getPaddingLeft();
+        int distFromEdgeOfScreen = grid.getWorkspacePadding().left + getPaddingLeft();
 
         if (grid.isPhone && (grid.availableWidthPx - width) < 4 * distFromEdgeOfScreen) {
             // Center the folder if it is very close to being centered anyway, by virtue of
@@ -1093,7 +1093,7 @@
 
     private int getContentAreaHeight() {
         DeviceProfile grid = mLauncher.getDeviceProfile();
-        Rect workspacePadding = grid.getWorkspacePadding(mContent.mIsRtl);
+        Rect workspacePadding = grid.getWorkspacePadding();
         int maxContentAreaHeight = grid.availableHeightPx -
                 workspacePadding.top - workspacePadding.bottom -
                 mFooterHeight;
diff --git a/src/com/android/launcher3/pageindicators/PageIndicatorLine.java b/src/com/android/launcher3/pageindicators/PageIndicatorLine.java
index bfb4f33..aec708c 100644
--- a/src/com/android/launcher3/pageindicators/PageIndicatorLine.java
+++ b/src/com/android/launcher3/pageindicators/PageIndicatorLine.java
@@ -27,18 +27,29 @@
 public class PageIndicatorLine extends PageIndicator {
     private static final String TAG = "PageIndicatorLine";
 
-    private static final int LINE_FADE_DURATION = ViewConfiguration.getScrollBarFadeDuration();
+    private static final int LINE_ANIMATE_DURATION = ViewConfiguration.getScrollBarFadeDuration();
     private static final int LINE_FADE_DELAY = ViewConfiguration.getScrollDefaultDelay();
     public static final int WHITE_ALPHA = (int) (0.70f * 255);
     public static final int BLACK_ALPHA = (int) (0.65f * 255);
 
+    private static final int LINE_ALPHA_ANIMATOR_INDEX = 0;
+    private static final int NUM_PAGES_ANIMATOR_INDEX = 1;
+    private static final int TOTAL_SCROLL_ANIMATOR_INDEX = 2;
+
+    private ValueAnimator[] mAnimators = new ValueAnimator[3];
+
     private final Handler mDelayedLineFadeHandler = new Handler(Looper.getMainLooper());
 
     private boolean mShouldAutoHide = true;
 
-    private ValueAnimator mLineAlphaAnimator;
-    private int mAlpha = 0;
-    private float mProgress = 0f;
+    // The alpha of the line when it is showing.
+    private int mActiveAlpha = 0;
+    // The alpha that the line is being animated to or already at (either 0 or mActiveAlpha).
+    private int mToAlpha;
+    // A float value representing the number of pages, to allow for an animation when it changes.
+    private float mNumPagesFloat;
+    private int mCurrentScroll;
+    private int mTotalScroll;
     private Paint mLinePaint;
 
     private static final Property<PageIndicatorLine, Integer> PAINT_ALPHA
@@ -55,6 +66,34 @@
         }
     };
 
+    private static final Property<PageIndicatorLine, Float> NUM_PAGES
+            = new Property<PageIndicatorLine, Float>(Float.class, "num_pages") {
+        @Override
+        public Float get(PageIndicatorLine obj) {
+            return obj.mNumPagesFloat;
+        }
+
+        @Override
+        public void set(PageIndicatorLine obj, Float numPages) {
+            obj.mNumPagesFloat = numPages;
+            obj.invalidate();
+        }
+    };
+
+    private static final Property<PageIndicatorLine, Integer> TOTAL_SCROLL
+            = new Property<PageIndicatorLine, Integer>(Integer.class, "total_scroll") {
+        @Override
+        public Integer get(PageIndicatorLine obj) {
+            return obj.mTotalScroll;
+        }
+
+        @Override
+        public void set(PageIndicatorLine obj, Integer totalScroll) {
+            obj.mTotalScroll = totalScroll;
+            obj.invalidate();
+        }
+    };
+
     private Runnable mHideLineRunnable = new Runnable() {
         @Override
         public void run() {
@@ -78,13 +117,15 @@
 
     @Override
     protected void onDraw(Canvas canvas) {
-        if (mNumPages == 0) {
+        if (mTotalScroll == 0 || mNumPagesFloat == 0) {
             return;
         }
 
+        // Compute and draw line rect.
+        float progress = Utilities.boundToRange(((float) mCurrentScroll) / mTotalScroll, 0f, 1f);
         int availableWidth = canvas.getWidth();
-        int lineWidth = availableWidth / mNumPages;
-        int lineLeft = (int) (mProgress * (availableWidth - lineWidth));
+        int lineWidth = (int) (availableWidth / mNumPagesFloat);
+        int lineLeft = (int) (progress * (availableWidth - lineWidth));
         int lineRight = lineLeft + lineWidth;
         canvas.drawRect(lineLeft, 0, lineRight, canvas.getHeight(), mLinePaint);
     }
@@ -94,9 +135,16 @@
         if (getAlpha() == 0) {
             return;
         }
-        animateLineToAlpha(mAlpha);
-        mProgress = Utilities.boundToRange(((float) currentScroll) / totalScroll, 0f, 1f);;
-        invalidate();
+        animateLineToAlpha(mActiveAlpha);
+
+        mCurrentScroll = currentScroll;
+        if (mTotalScroll == 0) {
+            mTotalScroll = totalScroll;
+        } else if (mTotalScroll != totalScroll) {
+            animateToTotalScroll(totalScroll);
+        } else {
+            invalidate();
+        }
 
         if (mShouldAutoHide) {
             hideAfterDelay();
@@ -114,7 +162,9 @@
 
     @Override
     protected void onPageCountChanged() {
-        invalidate();
+        if (Float.compare(mNumPages, mNumPagesFloat) != 0) {
+            animateToNumPages(mNumPages);
+        }
     }
 
     public void setShouldAutoHide(boolean shouldAutoHide) {
@@ -137,9 +187,9 @@
         if (color != Color.TRANSPARENT) {
             color = ColorUtils.setAlphaComponent(color, 255);
             if (color == Color.BLACK) {
-                mAlpha = BLACK_ALPHA;
+                mActiveAlpha = BLACK_ALPHA;
             } else if (color == Color.WHITE) {
-                mAlpha = WHITE_ALPHA;
+                mActiveAlpha = WHITE_ALPHA;
             } else {
                 Log.e(TAG, "Setting workspace page indicators to an unsupported color: #"
                         + Integer.toHexString(color));
@@ -150,22 +200,44 @@
     }
 
     private void animateLineToAlpha(int alpha) {
-        if (mLineAlphaAnimator != null) {
-            // An animation is already running, so ignore the new animation request unless we are
-            // trying to hide the line, in which case we always allow the animation.
-            if (alpha != 0) {
-                return;
-            }
-            mLineAlphaAnimator.cancel();
+        if (alpha == mToAlpha) {
+            // Ignore the new animation if it is going to the same alpha as the current animation.
+            return;
         }
-        mLineAlphaAnimator = ObjectAnimator.ofInt(this, PAINT_ALPHA, alpha);
-        mLineAlphaAnimator.addListener(new AnimatorListenerAdapter() {
+        mToAlpha = alpha;
+        setupAndRunAnimation(ObjectAnimator.ofInt(this, PAINT_ALPHA, alpha),
+                LINE_ALPHA_ANIMATOR_INDEX);
+    }
+
+    private void animateToNumPages(int numPages) {
+        setupAndRunAnimation(ObjectAnimator.ofFloat(this, NUM_PAGES, numPages),
+                NUM_PAGES_ANIMATOR_INDEX);
+    }
+
+    private void animateToTotalScroll(int totalScroll) {
+        setupAndRunAnimation(ObjectAnimator.ofInt(this, TOTAL_SCROLL, totalScroll),
+                TOTAL_SCROLL_ANIMATOR_INDEX);
+    }
+
+    /**
+     * Starts the given animator and stores it in the provided index in {@link #mAnimators} until
+     * the animation ends.
+     *
+     * If an animator is already at the index (i.e. it is already playing), it is canceled and
+     * replaced with the new animator.
+     */
+    private void setupAndRunAnimation(ValueAnimator animator, final int animatorIndex) {
+        if (mAnimators[animatorIndex] != null) {
+            mAnimators[animatorIndex].cancel();
+        }
+        mAnimators[animatorIndex] = animator;
+        mAnimators[animatorIndex].addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
-                mLineAlphaAnimator = null;
+                mAnimators[animatorIndex] = null;
             }
         });
-        mLineAlphaAnimator.setDuration(LINE_FADE_DURATION);
-        mLineAlphaAnimator.start();
+        mAnimators[animatorIndex].setDuration(LINE_ANIMATE_DURATION);
+        mAnimators[animatorIndex].start();
     }
 }
diff --git a/src/com/android/launcher3/util/TouchController.java b/src/com/android/launcher3/util/TouchController.java
new file mode 100644
index 0000000..d1409c8
--- /dev/null
+++ b/src/com/android/launcher3/util/TouchController.java
@@ -0,0 +1,8 @@
+package com.android.launcher3.util;
+
+import android.view.MotionEvent;
+
+public interface TouchController {
+    boolean onTouchEvent(MotionEvent ev);
+    boolean onInterceptTouchEvent(MotionEvent ev);
+}
diff --git a/tests/src/com/android/launcher3/InvariantDeviceProfileTest.java b/tests/src/com/android/launcher3/InvariantDeviceProfileTest.java
index 476eb0a..35f686f 100644
--- a/tests/src/com/android/launcher3/InvariantDeviceProfileTest.java
+++ b/tests/src/com/android/launcher3/InvariantDeviceProfileTest.java
@@ -116,75 +116,4 @@
     // Add more tests for other devices, however, running them once on a single device is enough
     // for verifying that for a platform version, the WindowManager and DisplayMetrics is
     // working as intended.
-
-    /**
-     * Make sure that the height for the QSB is what we expect in normal mode.
-     */
-    public void testQsbNormalHeight() {
-        Resources resources = getContext().getResources();
-        DeviceProfile landscapeProfile = mInvariantProfile.landscapeProfile;
-        DeviceProfile portraitProfile = mInvariantProfile.portraitProfile;
-        landscapeProfile.setSearchBarHeight(LauncherCallbacks.SEARCH_BAR_HEIGHT_NORMAL);
-        portraitProfile.setSearchBarHeight(LauncherCallbacks.SEARCH_BAR_HEIGHT_NORMAL);
-        Rect portraitBounds = portraitProfile.getSearchBarBounds(true); // RTL shouldn't matter.
-        int portraitHeight = (int) Utilities.dpiFromPx(portraitBounds.height(),
-                resources.getDisplayMetrics());
-        Rect landscapeBounds = landscapeProfile.getSearchBarBounds(true); // RTL shouldn't matter.
-        int landscapeHeight = (int) Utilities.dpiFromPx(landscapeBounds.height(),
-                resources.getDisplayMetrics());
-        if (portraitProfile.isTablet) {
-            assertEquals(8 + 48 + 24, portraitHeight);
-        } else {
-            assertEquals(8 + 48 + 12, portraitHeight);
-        }
-        // Make sure the height that we pass in the widget options bundle is the height of the
-        // search bar + 8dps padding top and bottom.
-        Point portraitDimens = portraitProfile.getSearchBarDimensForWidgetOpts(resources);
-        int portraitWidgetOptsHeight = portraitDimens.y;
-        Point landscapeDimens = landscapeProfile.getSearchBarDimensForWidgetOpts(resources);
-        int landscapeWidgetOptsHeight = landscapeDimens.y;
-        assertEquals(8 + 48 + 8, (int) Utilities.dpiFromPx(portraitWidgetOptsHeight,
-                resources.getDisplayMetrics()));
-        if (!landscapeProfile.isVerticalBarLayout()) {
-            assertEquals(portraitHeight, landscapeHeight);
-            assertEquals(portraitWidgetOptsHeight, landscapeWidgetOptsHeight);
-        }
-    }
-
-    /**
-     * Make sure that the height for the QSB is what we expect in tall mode.
-     */
-    public void testQsbTallHeight() {
-        Resources resources = getContext().getResources();
-        DeviceProfile landscapeProfile = mInvariantProfile.landscapeProfile;
-        DeviceProfile portraitProfile = mInvariantProfile.portraitProfile;
-        landscapeProfile.setSearchBarHeight(LauncherCallbacks.SEARCH_BAR_HEIGHT_TALL);
-        portraitProfile.setSearchBarHeight(LauncherCallbacks.SEARCH_BAR_HEIGHT_TALL);
-        Rect portraitBounds = portraitProfile.getSearchBarBounds(true); // RTL shouldn't matter.
-        int portraitHeight = (int) Utilities.dpiFromPx(portraitBounds.height(),
-                resources.getDisplayMetrics());
-        Rect landscapeBounds = landscapeProfile.getSearchBarBounds(true); // RTL shouldn't matter.
-        int landscapeHeight = (int) Utilities.dpiFromPx(landscapeBounds.height(),
-                resources.getDisplayMetrics());
-        if (portraitProfile.isPhone) {
-            // This fails on some devices due to http://b/26884580 (portraitHeight is 101, not 100).
-            // TODO: Remove the comparision against 101 once b/26884580 is fixed
-            // assertEquals(4 + 94 + 2, portraitHeight);
-            assertTrue(portraitHeight == (4 + 94 + 2) || portraitHeight == (4 + 95 + 2));
-        } else {
-            assertEquals(8 + 94 + 24, portraitHeight);
-        }
-        // Make sure the height that we pass in the widget options bundle is the height of the
-        // search bar + 8dps padding top and bottom.
-        Point portraitDimens = portraitProfile.getSearchBarDimensForWidgetOpts(resources);
-        int portraitWidgetOptsHeight = portraitDimens.y;
-        Point landscapeDimens = landscapeProfile.getSearchBarDimensForWidgetOpts(resources);
-        int landscapeWidgetOptsHeight = landscapeDimens.y;
-        assertEquals(8 + 94 + 8, (int) Utilities.dpiFromPx(portraitWidgetOptsHeight,
-                resources.getDisplayMetrics()));
-        if (!landscapeProfile.isVerticalBarLayout()) {
-            assertEquals(portraitHeight, landscapeHeight);
-            assertEquals(portraitWidgetOptsHeight, landscapeWidgetOptsHeight);
-        }
-    }
 }