Refactoring all apps search to support external search bar.

- Adding support for an external search bar that can be used
  to search a container view.  This adds a new interface
  AllAppsSearchController which manages the external search
  bar.  Each controller will have its own search implementation
  which means that we no longer need a common AppSearchManager
  interface.
- Removing elevation controller as we no longer have a builtin
  search bar in all apps
- Refactoring container view insets so that they behave
  the same in all containers.
- Refactoring apps view to ensure that we only update the number
  of columns with the available width
- Cleaning up LauncherCallbacks interface

Bug: 20127840
Bug: 21494973

Change-Id: I710b8e18196961d77d8a29f0c345531d480936fe
diff --git a/src/com/android/launcher3/BaseContainerView.java b/src/com/android/launcher3/BaseContainerView.java
index bd1c625..4b7b977 100644
--- a/src/com/android/launcher3/BaseContainerView.java
+++ b/src/com/android/launcher3/BaseContainerView.java
@@ -19,16 +19,25 @@
 import android.content.Context;
 import android.graphics.Rect;
 import android.util.AttributeSet;
-import android.widget.FrameLayout;
+import android.widget.LinearLayout;
 
 /**
  * A base container view, which supports resizing.
  */
-public class BaseContainerView extends FrameLayout implements Insettable {
+public abstract class BaseContainerView extends LinearLayout implements Insettable {
 
-    protected Rect mInsets = new Rect();
-    protected Rect mFixedBounds = new Rect();
-    protected int mFixedBoundsContainerInset;
+    // The window insets
+    private Rect mInsets = new Rect();
+    // The bounds of the search bar.  Only the left, top, right are used to inset the
+    // search bar and the height is determined by the measurement of the layout
+    private Rect mSearchBarBounds = new Rect();
+    // The bounds of the container
+    protected Rect mContentBounds = new Rect();
+    // The padding to apply to the container to achieve the bounds
+    protected Rect mContentPadding = new Rect();
+    // The inset to apply to the edges and between the search bar and the container
+    private int mContainerBoundsInset;
+    private boolean mHasSearchBar;
 
     public BaseContainerView(Context context) {
         this(context, null);
@@ -40,62 +49,73 @@
 
     public BaseContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
-        mFixedBoundsContainerInset = context.getResources().getDimensionPixelSize(
-                R.dimen.container_fixed_bounds_inset);
+        mContainerBoundsInset = getResources().getDimensionPixelSize(R.dimen.container_bounds_inset);
     }
 
     @Override
     final public void setInsets(Rect insets) {
         mInsets.set(insets);
-        onUpdateBackgrounds();
-        onUpdatePaddings();
+        updateBackgroundAndPaddings();
+    }
+
+    protected void setHasSearchBar() {
+        mHasSearchBar = true;
     }
 
     /**
-     * Sets the fixed bounds for this container view.
+     * Sets the search bar bounds for this container view to match.
      */
-    final public void setFixedBounds(Rect fixedBounds) {
-        if (!fixedBounds.isEmpty() && !fixedBounds.equals(mFixedBounds)) {
-            mFixedBounds.set(fixedBounds);
-            if (Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION) {
-                mFixedBounds.top = mInsets.top;
-                mFixedBounds.bottom = mInsets.bottom;
-            }
-            // To ensure that the child RecyclerView has the full width to handle touches right to
-            // the edge of the screen, we only apply the top and bottom padding to the bounds
-            mFixedBounds.top += mFixedBoundsContainerInset;
-            mFixedBounds.bottom += mFixedBoundsContainerInset;
-            onFixedBoundsUpdated();
-        }
+    final public void setSearchBarBounds(Rect bounds) {
+        mSearchBarBounds.set(bounds);
+
         // Post the updates since they can trigger a relayout, and this call can be triggered from
         // a layout pass itself.
         post(new Runnable() {
             @Override
             public void run() {
-                onUpdateBackgrounds();
-                onUpdatePaddings();
+                updateBackgroundAndPaddings();
             }
         });
     }
 
     /**
-     * Update the UI in response to a change in the fixed bounds.
+     * Update the backgrounds and padding in response to a change in the bounds or insets.
      */
-    protected void onFixedBoundsUpdated() {
-        // Do nothing
+    protected void updateBackgroundAndPaddings() {
+        Rect padding;
+        Rect searchBarBounds = new Rect(mSearchBarBounds);
+        if (mSearchBarBounds.isEmpty()) {
+            // Use the normal bounds
+            padding = new Rect(mInsets.left + mContainerBoundsInset,
+                    (mHasSearchBar ? 0 : (mInsets.top + mContainerBoundsInset)),
+                    mInsets.right + mContainerBoundsInset,
+                    mInsets.bottom + mContainerBoundsInset);
+
+            // Special case -- we have the search bar, but no specific bounds, so just give it
+            // the inset bounds without a height.
+            searchBarBounds.set(mInsets.left + mContainerBoundsInset,
+                    mInsets.top + mContainerBoundsInset,
+                    getMeasuredWidth() - (mInsets.right + mContainerBoundsInset), 0);
+        } else {
+            // Use the search bounds, if there is a search bar, the bounds will contain
+            // the offsets for the insets so we can ignore that
+            padding = new Rect(mSearchBarBounds.left,
+                    (mHasSearchBar ? 0 : (mInsets.top + mContainerBoundsInset)),
+                    getMeasuredWidth() - mSearchBarBounds.right,
+                    mInsets.bottom + mContainerBoundsInset);
+        }
+        if (!padding.equals(mContentPadding) || !searchBarBounds.equals(mSearchBarBounds)) {
+            mContentPadding.set(padding);
+            mContentBounds.set(padding.left, padding.top,
+                    getMeasuredWidth() - padding.right,
+                    getMeasuredHeight() - padding.bottom);
+            mSearchBarBounds.set(searchBarBounds);
+            onUpdateBackgroundAndPaddings(mSearchBarBounds, padding);
+        }
     }
 
     /**
-     * Update the paddings in response to a change in the bounds or insets.
+     * To be implemented by container views to update themselves when the bounds changes.
      */
-    protected void onUpdatePaddings() {
-        // Do nothing
-    }
-
-    /**
-     * Update the backgrounds in response to a change in the bounds or insets.
-     */
-    protected void onUpdateBackgrounds() {
-        // Do nothing
-    }
+    protected abstract void onUpdateBackgroundAndPaddings(Rect searchBarBounds, Rect padding);
 }
\ No newline at end of file
diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java
index a207d9a..081c4f5 100644
--- a/src/com/android/launcher3/BaseRecyclerView.java
+++ b/src/com/android/launcher3/BaseRecyclerView.java
@@ -89,9 +89,7 @@
     private int mLastY;
     private int mScrollbarWidth;
     private int mScrollbarInset;
-    private Rect mBackgroundPadding = new Rect();
-
-
+    protected Rect mBackgroundPadding = new Rect();
 
     public BaseRecyclerView(Context context) {
         this(context, null);
@@ -230,6 +228,10 @@
         return false;
     }
 
+    public void updateBackgroundPadding(Rect padding) {
+        mBackgroundPadding.set(padding);
+    }
+
     @Override
     protected void dispatchDraw(Canvas canvas) {
         super.dispatchDraw(canvas);
@@ -340,9 +342,10 @@
             // Calculate the position for the fast scroller popup
             Rect bgBounds = mFastScrollerBg.getBounds();
             if (Utilities.isRtl(getResources())) {
-                x = mBackgroundPadding.left + getScrollBarSize();
+                x = mBackgroundPadding.left + (2 * getScrollbarWidth());
             } else {
-                x = getWidth() - getPaddingRight() - getScrollBarSize() - bgBounds.width();
+                x = getWidth() - mBackgroundPadding.right - (2 * getScrollbarWidth()) -
+                        bgBounds.width();
             }
             y = mLastY - (int) (FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR * bgBounds.height());
             y = Math.max(getPaddingTop(), Math.min(y, getHeight() - getPaddingBottom() -
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 9c59dab..a50540d 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -223,26 +223,22 @@
         folderCellHeightPx = cellHeightPx + edgeMarginPx;
         folderBackgroundOffset = -edgeMarginPx;
         folderIconSizePx = iconSizePx + 2 * -folderBackgroundOffset;
-
-        updateAppsViewNumCols(res, 0);
     }
 
-    public boolean updateAppsViewNumCols(Resources res, int containerWidth) {
+    /**
+     * @param recyclerViewWidth the available width of the AllAppsRecyclerView
+     */
+    public void updateAppsViewNumCols(Resources res, int recyclerViewWidth) {
         int appsViewLeftMarginPx =
                 res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin);
         int allAppsCellWidthGap =
                 res.getDimensionPixelSize(R.dimen.all_apps_icon_width_gap);
-        int availableAppsWidthPx = (containerWidth > 0) ? containerWidth : availableWidthPx;
+        int availableAppsWidthPx = (recyclerViewWidth > 0) ? recyclerViewWidth : availableWidthPx;
         int numAppsCols = (availableAppsWidthPx - appsViewLeftMarginPx) /
                 (allAppsIconSizePx + allAppsCellWidthGap);
         int numPredictiveAppCols = Math.max(inv.minAllAppsPredictionColumns, numAppsCols);
-        if ((numAppsCols != allAppsNumCols) ||
-                (numPredictiveAppCols != allAppsNumPredictiveCols)) {
-            allAppsNumCols = numAppsCols;
-            allAppsNumPredictiveCols = numPredictiveAppCols;
-            return true;
-        }
-        return false;
+        allAppsNumCols = numAppsCols;
+        allAppsNumPredictiveCols = numPredictiveAppCols;
     }
 
     /** Returns the search bar top offset */
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 796de3f..335a77b 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -99,7 +99,7 @@
 import com.android.launcher3.DropTarget.DragObject;
 import com.android.launcher3.PagedView.PageSwitchListener;
 import com.android.launcher3.allapps.AllAppsContainerView;
-import com.android.launcher3.allapps.AppSearchManager;
+import com.android.launcher3.allapps.AllAppsSearchBarController;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 import com.android.launcher3.compat.LauncherActivityInfoCompat;
 import com.android.launcher3.compat.LauncherAppsCompat;
@@ -138,9 +138,6 @@
     static final String TAG = "Launcher";
     static final boolean LOGD = false;
 
-    // Temporary flag
-    static final boolean DISABLE_ALL_APPS_SEARCH_INTEGRATION = true;
-
     static final boolean PROFILE_STARTUP = false;
     static final boolean DEBUG_WIDGETS = true;
     static final boolean DEBUG_STRICT_MODE = false;
@@ -573,32 +570,6 @@
 
     public boolean setLauncherCallbacks(LauncherCallbacks callbacks) {
         mLauncherCallbacks = callbacks;
-        mLauncherCallbacks.setLauncherAppsCallback(new Launcher.LauncherAppsCallbacks() {
-            @Override
-            public void onAllAppsBoundsChanged(Rect bounds) {
-                if (LOGD) {
-                    Log.d(TAG, "onAllAppsBoundsChanged(Rect): " + bounds);
-                }
-                mAppsView.setFixedBounds(bounds);
-                mWidgetsView.setFixedBounds(bounds);
-            }
-
-            @Override
-            public void dismissAllApps() {
-                if (!DISABLE_ALL_APPS_SEARCH_INTEGRATION) {
-                    // Dismiss All Apps if we aren't already paused/invisible
-                    if (!mPaused) {
-                        showWorkspace(WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, true,
-                                null /* onCompleteRunnable */, false /* notifyLauncherCallbacks */);
-                    }
-                }
-            }
-
-            @Override
-            public void setSearchManager(AppSearchManager manager) {
-                mAppsView.setSearchManager(manager);
-            }
-        });
         mLauncherCallbacks.setLauncherSearchCallback(new Launcher.LauncherSearchCallbacks() {
             private boolean mImportanceStored = false;
             private int mWorkspaceImportanceForAccessibility =
@@ -638,6 +609,14 @@
         }
     }
 
+    /**
+     * Updates the bounds of all the overlays to match the new fixed bounds.
+     */
+    public void updateOverlayBounds(Rect newBounds) {
+        mAppsView.setSearchBarBounds(newBounds);
+        mWidgetsView.setSearchBarBounds(newBounds);
+    }
+
     /** To be overridden by subclasses to hint to Launcher that we have custom content */
     protected boolean hasCustomContentToLeft() {
         if (mLauncherCallbacks != null) {
@@ -1012,16 +991,6 @@
         }
         mOnResumeState = State.NONE;
 
-        // Restore the apps state if we are in all apps
-        if (!Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION) {
-            // Otherwise, notify the callbacks if we are in all apps mode
-            if (mState == State.APPS) {
-                if (mLauncherCallbacks != null) {
-                    mLauncherCallbacks.onAllAppsShown();
-                }
-            }
-        }
-
         // Background was set to gradient in onPause(), restore to transparent if in all apps.
         setWorkspaceBackground(mState == State.WORKSPACE ? WORKSPACE_BACKGROUND_GRADIENT
                 : WORKSPACE_BACKGROUND_TRANSPARENT);
@@ -1167,17 +1136,20 @@
          * Updates launcher to the available space that AllApps can take so as not to overlap with
          * any other views.
          */
+        @Deprecated
         public void onAllAppsBoundsChanged(Rect bounds);
 
         /**
          * Called to dismiss all apps if it is showing.
          */
+        @Deprecated
         public void dismissAllApps();
 
         /**
          * Sets the search manager to be used for app search.
          */
-        public void setSearchManager(AppSearchManager manager);
+        @Deprecated
+        public void setSearchManager(Object manager);
     }
 
     public interface LauncherSearchCallbacks {
@@ -1463,14 +1435,14 @@
         mSearchDropTargetBar = (SearchDropTargetBar)
                 mDragLayer.findViewById(R.id.search_drop_target_bar);
 
-        // Setup Apps
+        // Setup Apps and Widgets
         mAppsView = (AllAppsContainerView) findViewById(R.id.apps_view);
-        if (isAllAppsSearchOverridden()) {
-            mAppsView.hideHeaderBar();
-        }
-
-        // Setup AppsCustomize
         mWidgetsView = (WidgetsContainerView) findViewById(R.id.widgets_view);
+        if (mLauncherCallbacks != null && mLauncherCallbacks.getAllAppsSearchBarController() != null) {
+            mAppsView.setSearchBarController(mLauncherCallbacks.getAllAppsSearchBarController());
+        } else {
+            mAppsView.setSearchBarController(mAppsView.newDefaultAppSearchController());
+        }
 
         // Setup the drag controller (drop targets have to be added in reverse order in priority)
         dragController.setDragScoller(mWorkspace);
@@ -2866,17 +2838,8 @@
     public void updateInteraction(Workspace.State fromState, Workspace.State toState) {
         // Only update the interacting state if we are transitioning to/from a view with an
         // overlay
-        boolean fromStateWithOverlay;
-        boolean toStateWithOverlay;
-        if (Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION) {
-            fromStateWithOverlay = fromState != Workspace.State.NORMAL;
-            toStateWithOverlay = toState != Workspace.State.NORMAL;
-        } else {
-            fromStateWithOverlay = fromState != Workspace.State.NORMAL &&
-                    fromState != Workspace.State.NORMAL_HIDDEN;
-            toStateWithOverlay = toState != Workspace.State.NORMAL &&
-                    toState != Workspace.State.NORMAL_HIDDEN;
-        }
+        boolean fromStateWithOverlay = fromState != Workspace.State.NORMAL;
+        boolean toStateWithOverlay = toState != Workspace.State.NORMAL;
         if (toStateWithOverlay) {
             onInteractionBegin();
         } else if (fromStateWithOverlay) {
@@ -3320,21 +3283,19 @@
     }
 
     public void showWorkspace(boolean animated) {
-        showWorkspace(WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, animated, null,
-                true);
+        showWorkspace(WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, animated, null);
     }
 
     public void showWorkspace(boolean animated, Runnable onCompleteRunnable) {
         showWorkspace(WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, animated,
-                onCompleteRunnable, true);
+                onCompleteRunnable);
     }
 
     protected void showWorkspace(int snapToPage, boolean animated) {
-        showWorkspace(snapToPage, animated, null, true);
+        showWorkspace(snapToPage, animated, null);
     }
 
-    void showWorkspace(int snapToPage, boolean animated, Runnable onCompleteRunnable,
-            boolean notifyLauncherCallbacks) {
+    void showWorkspace(int snapToPage, boolean animated, Runnable onCompleteRunnable) {
         boolean changed = mState != State.WORKSPACE ||
                 mWorkspace.getState() != Workspace.State.NORMAL;
         if (changed) {
@@ -3366,12 +3327,6 @@
             // Send an accessibility event to announce the context change
             getWindow().getDecorView()
                     .sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
-            if (notifyLauncherCallbacks) {
-                // Dismiss all apps when the workspace is shown
-                if (!Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION && mLauncherCallbacks != null) {
-                    mLauncherCallbacks.onAllAppsHidden();
-                }
-            }
         }
     }
 
@@ -3431,10 +3386,7 @@
         }
 
         if (toState == State.APPS) {
-            mStateTransitionAnimation.startAnimationToAllApps(animated);
-            if (!Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION && mLauncherCallbacks != null) {
-                mLauncherCallbacks.onAllAppsShown();
-            }
+            mStateTransitionAnimation.startAnimationToAllApps(mState, animated);
         } else {
             mStateTransitionAnimation.startAnimationToWidgets(animated);
         }
@@ -3458,9 +3410,10 @@
      * new state.
      */
     public Animator startWorkspaceStateChangeAnimation(Workspace.State toState, int toPage,
-            boolean animated, HashMap<View, Integer> layerViews) {
+            boolean animated, boolean hasOverlaySearchBar, HashMap<View, Integer> layerViews) {
         Workspace.State fromState = mWorkspace.getState();
-        Animator anim = mWorkspace.setStateWithAnimation(toState, toPage, animated, layerViews);
+        Animator anim = mWorkspace.setStateWithAnimation(toState, toPage, animated,
+                hasOverlaySearchBar, layerViews);
         updateInteraction(fromState, toState);
         return anim;
     }
@@ -3482,14 +3435,6 @@
             final Runnable onCompleteRunnable) {
         if (mState != State.APPS_SPRING_LOADED && mState != State.WIDGETS_SPRING_LOADED) return;
 
-        if (successfulDrop) {
-            // We need to trigger all apps hidden to notify search to update itself before the
-            // delayed call to showWorkspace below
-            if (!Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION && mLauncherCallbacks != null) {
-                mLauncherCallbacks.onAllAppsHidden();
-            }
-        }
-
         mHandler.postDelayed(new Runnable() {
             @Override
             public void run() {
@@ -4473,20 +4418,6 @@
         return null;
     }
 
-    /**
-     * Returns whether the launcher callbacks overrides search in all apps.
-     */
-    @Thunk boolean isAllAppsSearchOverridden() {
-        if (DISABLE_ALL_APPS_SEARCH_INTEGRATION) {
-            return false;
-        }
-
-        if (mLauncherCallbacks != null) {
-            return mLauncherCallbacks.overrideAllAppsSearch();
-        }
-        return false;
-    }
-
     private boolean shouldRunFirstRunActivity() {
         return !ActivityManager.isRunningInTestHarness() &&
                 !mSharedPrefs.getBoolean(FIRST_RUN_ACTIVITY_DISPLAYED, false);
diff --git a/src/com/android/launcher3/LauncherCallbacks.java b/src/com/android/launcher3/LauncherCallbacks.java
index 70e400b..e732754 100644
--- a/src/com/android/launcher3/LauncherCallbacks.java
+++ b/src/com/android/launcher3/LauncherCallbacks.java
@@ -7,6 +7,7 @@
 import android.view.Menu;
 import android.view.View;
 import android.view.ViewGroup;
+import com.android.launcher3.allapps.AllAppsSearchBarController;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -51,12 +52,9 @@
     public void onLauncherProviderChange();
     public void finishBindingItems(final boolean upgradePath);
     public void onClickAllAppsButton(View v);
-    public void onAllAppsShown();
-    public void onAllAppsHidden();
     public void bindAllApplications(ArrayList<AppInfo> apps);
     public void onClickFolderIcon(View v);
     public void onClickAppShortcut(View v);
-
     @Deprecated
     public void onClickPagedViewIcon(View v);
     public void onClickWallpaperPicker(View v);
@@ -89,10 +87,11 @@
     public View getIntroScreen();
     public boolean shouldMoveToDefaultScreenOnHomeIntent();
     public boolean hasSettings();
+    @Deprecated
     public ComponentName getWallpaperPickerComponent();
     public boolean overrideWallpaperDimensions();
     public boolean isLauncherPreinstalled();
-    public boolean overrideAllAppsSearch();
+    public AllAppsSearchBarController getAllAppsSearchBarController();
     public List<ComponentName> getPredictedApps();
 
     /**
@@ -114,14 +113,6 @@
             Launcher.LauncherOverlayCallbacks callbacks);
 
     /**
-     * Sets the callbacks to allow any extensions to callback to the launcher.
-     *
-     * @param callbacks A set of callbacks to the Launcher, is actually a LauncherAppsCallback, but
-     *                  for implementation purposes is passed around as an object.
-     */
-    public void setLauncherAppsCallback(Object callbacks);
-
-    /**
      * Sets the callbacks to allow reacting the actions of search overlays of the launcher.
      *
      * @param callbacks A set of callbacks to the Launcher, is actually a LauncherSearchCallback,
diff --git a/src/com/android/launcher3/LauncherExtension.java b/src/com/android/launcher3/LauncherExtension.java
index 09a105b..fafb070 100644
--- a/src/com/android/launcher3/LauncherExtension.java
+++ b/src/com/android/launcher3/LauncherExtension.java
@@ -11,6 +11,7 @@
 import android.view.Menu;
 import android.view.View;
 import android.view.ViewGroup;
+import com.android.launcher3.allapps.AllAppsSearchBarController;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -125,14 +126,6 @@
         }
 
         @Override
-        public void onAllAppsShown() {
-        }
-
-        @Override
-        public void onAllAppsHidden() {
-        }
-
-        @Override
         public void bindAllApplications(ArrayList<AppInfo> apps) {
         }
 
@@ -255,8 +248,8 @@
         }
 
         @Override
-        public boolean overrideAllAppsSearch() {
-            return false;
+        public AllAppsSearchBarController getAllAppsSearchBarController() {
+            return null;
         }
 
         @Override
@@ -285,11 +278,6 @@
         }
 
         @Override
-        public void setLauncherAppsCallback(Object callbacks) {
-            // Do nothing
-        }
-
-        @Override
         public void setLauncherSearchCallback(Object callbacks) {
             // Do nothing
         }
diff --git a/src/com/android/launcher3/LauncherStateTransitionAnimation.java b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
index a006d14..e94a2ac 100644
--- a/src/com/android/launcher3/LauncherStateTransitionAnimation.java
+++ b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
@@ -129,7 +129,7 @@
     /**
      * Starts an animation to the apps view.
      */
-    public void startAnimationToAllApps(final boolean animated) {
+    public void startAnimationToAllApps(final Launcher.State fromState, final boolean animated) {
         final AllAppsContainerView toView = mLauncher.getAppsView();
         PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks() {
             private int[] mAllAppsToPanelDelta;
@@ -137,7 +137,6 @@
             @Override
             public void onRevealViewVisible(View revealView, View contentView,
                     View allAppsButtonView) {
-                toView.setBackground(null);
                 // Get the y delta between the center of the page and the center of the all apps
                 // button
                 mAllAppsToPanelDelta = Utilities.getCenterDeltaInScreenSpace(revealView,
@@ -173,9 +172,9 @@
                 };
             }
         };
+        // Only animate the search bar if animating from spring loaded mode back to all apps
         startAnimationToOverlay(Workspace.State.NORMAL_HIDDEN, toView, toView.getContentView(),
-                toView.getRevealView(), animated,
-                !mLauncher.isAllAppsSearchOverridden() /* hideSearchBar */, cb);
+                toView.getRevealView(), toView.getSearchBarView(), animated, true, cb);
     }
 
     /**
@@ -188,7 +187,6 @@
             @Override
             public void onRevealViewVisible(View revealView, View contentView,
                     View allAppsButtonView) {
-                revealView.setBackground(res.getDrawable(R.drawable.quantum_panel_dark));
             }
             @Override
             public float getMaterialRevealViewFinalAlpha(View revealView) {
@@ -200,8 +198,8 @@
             }
         };
         startAnimationToOverlay(Workspace.State.OVERVIEW_HIDDEN, toView,
-                toView.getContentView(), toView.getRevealView(), animated, true /* hideSearchBar */,
-                cb);
+                toView.getContentView(), toView.getRevealView(), null, animated,
+                true /* hideSearchBar */, cb);
     }
 
     /**
@@ -217,10 +215,10 @@
         }
 
         if (fromState == Launcher.State.APPS || fromState == Launcher.State.APPS_SPRING_LOADED) {
-            startAnimationToWorkspaceFromAllApps(fromState, toWorkspaceState, toWorkspacePage,
+            startAnimationToWorkspaceFromAllApps(toWorkspaceState, toWorkspacePage,
                     animated, onCompleteRunnable);
         } else {
-            startAnimationToWorkspaceFromWidgets(fromState, toWorkspaceState, toWorkspacePage,
+            startAnimationToWorkspaceFromWidgets(toWorkspaceState, toWorkspacePage,
                     animated, onCompleteRunnable);
         }
     }
@@ -230,8 +228,9 @@
      */
     @SuppressLint("NewApi")
     private void startAnimationToOverlay(final Workspace.State toWorkspaceState, final View toView,
-             final View contentView, final View revealView, final boolean animated,
-             final boolean hideSearchBar, final PrivateTransitionCallbacks pCb) {
+             final View contentView, final View revealView, final View overlaySearchBarView,
+             final boolean animated, final boolean hideSearchBar,
+             final PrivateTransitionCallbacks pCb) {
         final Resources res = mLauncher.getResources();
         final boolean material = Utilities.isLmpOrAbove();
         final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime);
@@ -252,7 +251,7 @@
         // Create the workspace animation.
         // NOTE: this call apparently also sets the state for the workspace if !animated
         Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState, -1,
-                animated, layerViews);
+                animated, overlaySearchBarView != null /* hasOverlaySearchBar */, layerViews);
 
         if (animated && initialized) {
             mStateAnimation = LauncherAnimUtils.createAnimatorSet();
@@ -297,6 +296,15 @@
             layerViews.put(revealView, BUILD_AND_SET_LAYER);
             mStateAnimation.play(panelAlphaAndDrift);
 
+            if (overlaySearchBarView != null) {
+                overlaySearchBarView.setAlpha(0f);
+                ObjectAnimator searchBarAlpha = ObjectAnimator.ofFloat(overlaySearchBarView, "alpha", 0f, 1f);
+                searchBarAlpha.setDuration(100);
+                searchBarAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
+                layerViews.put(overlaySearchBarView, BUILD_AND_SET_LAYER);
+                mStateAnimation.play(searchBarAlpha);
+            }
+
             // Setup the animation for the content view
             contentView.setVisibility(View.VISIBLE);
             contentView.setAlpha(0f);
@@ -426,9 +434,8 @@
     /**
      * Starts and animation to the workspace from the apps view.
      */
-    private void startAnimationToWorkspaceFromAllApps(final Launcher.State fromState,
-              final Workspace.State toWorkspaceState, final int toWorkspacePage,
-              final boolean animated, final Runnable onCompleteRunnable) {
+    private void startAnimationToWorkspaceFromAllApps(final Workspace.State toWorkspaceState,
+            final int toWorkspacePage, final boolean animated, final Runnable onCompleteRunnable) {
         AllAppsContainerView appsView = mLauncher.getAppsView();
         PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks() {
             int[] mAllAppsToPanelDelta;
@@ -479,24 +486,23 @@
                 };
             }
         };
+        // Only animate the search bar if animating to spring loaded mode from all apps
         startAnimationToWorkspaceFromOverlay(toWorkspaceState, toWorkspacePage, appsView,
-                appsView.getContentView(), appsView.getRevealView(), animated, onCompleteRunnable,
-                cb);
+                appsView.getContentView(), appsView.getRevealView(), appsView.getSearchBarView(),
+                animated, onCompleteRunnable, cb);
     }
 
     /**
      * Starts and animation to the workspace from the widgets view.
      */
-    private void startAnimationToWorkspaceFromWidgets(final Launcher.State fromState,
-              final Workspace.State toWorkspaceState, final int toWorkspacePage,
-              final boolean animated, final Runnable onCompleteRunnable) {
+    private void startAnimationToWorkspaceFromWidgets(final Workspace.State toWorkspaceState,
+              final int toWorkspacePage, final boolean animated, final Runnable onCompleteRunnable) {
         final WidgetsContainerView widgetsView = mLauncher.getWidgetsView();
         final Resources res = mLauncher.getResources();
         PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks() {
             @Override
             public void onRevealViewVisible(View revealView, View contentView,
                                             View allAppsButtonView) {
-                revealView.setBackground(res.getDrawable(R.drawable.quantum_panel_dark));
             }
             @Override
             public float getMaterialRevealViewFinalYDrift(View revealView) {
@@ -518,7 +524,7 @@
             }
         };
         startAnimationToWorkspaceFromOverlay(toWorkspaceState, toWorkspacePage, widgetsView,
-                widgetsView.getContentView(), widgetsView.getRevealView(), animated,
+                widgetsView.getContentView(), widgetsView.getRevealView(), null, animated,
                 onCompleteRunnable, cb);
     }
 
@@ -527,8 +533,8 @@
      */
     private void startAnimationToWorkspaceFromOverlay(final Workspace.State toWorkspaceState,
               final int toWorkspacePage, final View fromView, final View contentView,
-              final View revealView, final boolean animated, final Runnable onCompleteRunnable,
-              final PrivateTransitionCallbacks pCb) {
+              final View revealView, final View overlaySearchBarView, final boolean animated,
+              final Runnable onCompleteRunnable, final PrivateTransitionCallbacks pCb) {
         final Resources res = mLauncher.getResources();
         final boolean material = Utilities.isLmpOrAbove();
         final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime);
@@ -549,7 +555,8 @@
         // Create the workspace animation.
         // NOTE: this call apparently also sets the state for the workspace if !animated
         Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState,
-                toWorkspacePage, animated, layerViews);
+                toWorkspacePage, animated, overlaySearchBarView != null /* hasOverlaySearchBar */,
+                layerViews);
 
         if (animated && initialized) {
             mStateAnimation = LauncherAnimUtils.createAnimatorSet();
@@ -633,6 +640,16 @@
                 itemsAlpha.setInterpolator(decelerateInterpolator);
                 mStateAnimation.play(itemsAlpha);
 
+                if (overlaySearchBarView != null) {
+                    overlaySearchBarView.setAlpha(1f);
+                    ObjectAnimator searchAlpha = ObjectAnimator.ofFloat(overlaySearchBarView, "alpha", 1f, 0f);
+                    searchAlpha.setDuration(material ? 100 : 150);
+                    searchAlpha.setInterpolator(decelerateInterpolator);
+                    searchAlpha.setStartDelay(material ? 0 : itemsAlphaStagger + SINGLE_FRAME_DELAY);
+                    layerViews.put(overlaySearchBarView, BUILD_AND_SET_LAYER);
+                    mStateAnimation.play(searchAlpha);
+                }
+
                 if (material) {
                     // Animate the all apps button
                     float finalRadius = pCb.getMaterialRevealViewStartFinalRadius();
@@ -681,6 +698,9 @@
                         contentView.setTranslationY(0);
                         contentView.setAlpha(1);
                     }
+                    if (overlaySearchBarView != null) {
+                        overlaySearchBarView.setAlpha(1f);
+                    }
 
                     // This can hold unnecessary references to views.
                     mStateAnimation = null;
diff --git a/src/com/android/launcher3/LauncherTransitionable.java b/src/com/android/launcher3/LauncherTransitionable.java
index 9442abc..49af692 100644
--- a/src/com/android/launcher3/LauncherTransitionable.java
+++ b/src/com/android/launcher3/LauncherTransitionable.java
@@ -16,13 +16,10 @@
 
 package com.android.launcher3;
 
-import android.view.View;
-
 /**
  * An interface to get callbacks during a launcher transition.
  */
 public interface LauncherTransitionable {
-    View getContent();
     void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace);
     void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace);
     void onLauncherTransitionStep(Launcher l, float t);
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 19195b4..76f872b 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -1998,10 +1998,10 @@
      * to that new state.
      */
     public Animator setStateWithAnimation(State toState, int toPage, boolean animated,
-                                          HashMap<View, Integer> layerViews) {
+            boolean hasOverlaySearchBar, HashMap<View, Integer> layerViews) {
         // Create the animation to the new state
         Animator workspaceAnim =  mStateTransitionAnimation.getAnimationToState(getState(),
-                toState, toPage, animated, layerViews);
+                toState, toPage, animated, hasOverlaySearchBar, layerViews);
 
         // Update the current state
         mState = toState;
@@ -2100,11 +2100,6 @@
         }
     }
 
-    @Override
-    public View getContent() {
-        return this;
-    }
-
     /**
      * Returns the drawable for the given text view.
      */
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index e360e88..13e4a59 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -122,6 +122,52 @@
 }
 
 /**
+ * Stores the transition states for convenience.
+ */
+class TransitionStates {
+
+    // Raw states
+    final boolean oldStateIsNormal;
+    final boolean oldStateIsSpringLoaded;
+    final boolean oldStateIsNormalHidden;
+    final boolean oldStateIsOverviewHidden;
+    final boolean oldStateIsOverview;
+
+    final boolean stateIsNormal;
+    final boolean stateIsSpringLoaded;
+    final boolean stateIsNormalHidden;
+    final boolean stateIsOverviewHidden;
+    final boolean stateIsOverview;
+
+    // Convenience members
+    final boolean workspaceToAllApps;
+    final boolean overviewToAllApps;
+    final boolean allAppsToWorkspace;
+    final boolean workspaceToOverview;
+    final boolean overviewToWorkspace;
+
+    public TransitionStates(final Workspace.State fromState, final Workspace.State toState) {
+        oldStateIsNormal = (fromState == Workspace.State.NORMAL);
+        oldStateIsSpringLoaded = (fromState == Workspace.State.SPRING_LOADED);
+        oldStateIsNormalHidden = (fromState == Workspace.State.NORMAL_HIDDEN);
+        oldStateIsOverviewHidden = (fromState == Workspace.State.OVERVIEW_HIDDEN);
+        oldStateIsOverview = (fromState == Workspace.State.OVERVIEW);
+
+        stateIsNormal = (toState == Workspace.State.NORMAL);
+        stateIsSpringLoaded = (toState == Workspace.State.SPRING_LOADED);
+        stateIsNormalHidden = (toState == Workspace.State.NORMAL_HIDDEN);
+        stateIsOverviewHidden = (toState == Workspace.State.OVERVIEW_HIDDEN);
+        stateIsOverview = (toState == Workspace.State.OVERVIEW);
+
+        workspaceToOverview = (oldStateIsNormal && stateIsOverview);
+        workspaceToAllApps = (oldStateIsNormal && stateIsNormalHidden);
+        overviewToWorkspace = (oldStateIsOverview && stateIsNormal);
+        overviewToAllApps = (oldStateIsOverview && stateIsOverviewHidden);
+        allAppsToWorkspace = (stateIsNormalHidden && stateIsNormal);
+    }
+}
+
+/**
  * Manages the animations between each of the workspace states.
  */
 public class WorkspaceStateTransitionAnimation {
@@ -175,9 +221,17 @@
     }
 
     public AnimatorSet getAnimationToState(Workspace.State fromState, Workspace.State toState,
-                                           int toPage, boolean animated,
-                                           HashMap<View, Integer> layerViews) {
-        getAnimation(fromState, toState, toPage, animated, layerViews);
+            int toPage, boolean animated, boolean hasOverlaySearchBar,
+            HashMap<View, Integer> layerViews) {
+        AccessibilityManager am = (AccessibilityManager)
+                mLauncher.getSystemService(Context.ACCESSIBILITY_SERVICE);
+        final boolean accessibilityEnabled = am.isEnabled();
+        TransitionStates states = new TransitionStates(fromState, toState);
+        int duration = getAnimationDuration(states);
+        animateWorkspace(states, toPage, animated, duration, layerViews,
+                accessibilityEnabled);
+        animateSearchBar(states, animated, duration, hasOverlaySearchBar, layerViews,
+                accessibilityEnabled);
         return mStateAnimator;
     }
 
@@ -186,15 +240,37 @@
     }
 
     /**
+     * Reinitializes the arrays that we need for the animations on each page.
+     */
+    private void reinitializeAnimationArrays() {
+        final int childCount = mWorkspace.getChildCount();
+        if (mLastChildCount == childCount) return;
+
+        mOldBackgroundAlphas = new float[childCount];
+        mOldAlphas = new float[childCount];
+        mNewBackgroundAlphas = new float[childCount];
+        mNewAlphas = new float[childCount];
+    }
+
+    /**
+     * Returns the proper animation duration for a transition.
+     */
+    private int getAnimationDuration(TransitionStates states) {
+        if (states.workspaceToAllApps || states.overviewToAllApps) {
+            return mAllAppsTransitionTime;
+        } else if (states.workspaceToOverview || states.overviewToWorkspace) {
+            return mOverviewTransitionTime;
+        } else {
+            return mOverlayTransitionTime;
+        }
+    }
+
+    /**
      * Starts a transition animation for the workspace.
      */
-    private void getAnimation(final Workspace.State fromState, final Workspace.State toState,
-                              int toPage, final boolean animated,
-                              final HashMap<View, Integer> layerViews) {
-        AccessibilityManager am = (AccessibilityManager)
-                mLauncher.getSystemService(Context.ACCESSIBILITY_SERVICE);
-        final boolean accessibilityEnabled = am.isEnabled();
-
+    private void animateWorkspace(final TransitionStates states, int toPage, final boolean animated,
+                                  final int duration, final HashMap<View, Integer> layerViews,
+                                  final boolean accessibilityEnabled) {
         // Reinitialize animation arrays for the current workspace state
         reinitializeAnimationArrays();
 
@@ -205,32 +281,12 @@
         }
 
         // Update the workspace state
-        final boolean oldStateIsNormal = (fromState == Workspace.State.NORMAL);
-        final boolean oldStateIsSpringLoaded = (fromState == Workspace.State.SPRING_LOADED);
-        final boolean oldStateIsNormalHidden = (fromState == Workspace.State.NORMAL_HIDDEN);
-        final boolean oldStateIsOverviewHidden = (fromState == Workspace.State.OVERVIEW_HIDDEN);
-        final boolean oldStateIsOverview = (fromState == Workspace.State.OVERVIEW);
-
-        final boolean stateIsNormal = (toState == Workspace.State.NORMAL);
-        final boolean stateIsSpringLoaded = (toState == Workspace.State.SPRING_LOADED);
-        final boolean stateIsNormalHidden = (toState == Workspace.State.NORMAL_HIDDEN);
-        final boolean stateIsOverviewHidden = (toState == Workspace.State.OVERVIEW_HIDDEN);
-        final boolean stateIsOverview = (toState == Workspace.State.OVERVIEW);
-
-        final boolean workspaceToAllApps = (oldStateIsNormal && stateIsNormalHidden);
-        final boolean overviewToAllApps = (oldStateIsOverview && stateIsOverviewHidden);
-        final boolean allAppsToWorkspace = (stateIsNormalHidden && stateIsNormal);
-        final boolean workspaceToOverview = (oldStateIsNormal && stateIsOverview);
-        final boolean overviewToWorkspace = (oldStateIsOverview && stateIsNormal);
-
-        float finalBackgroundAlpha = (stateIsSpringLoaded || stateIsOverview) ? 1.0f : 0f;
-        float finalHotseatAndPageIndicatorAlpha = (stateIsNormal || stateIsSpringLoaded) ? 1f : 0f;
-        float finalOverviewPanelAlpha = stateIsOverview ? 1f : 0f;
-        // We keep the search bar visible on the workspace and in AllApps now
-        boolean showSearchBar = stateIsNormal ||
-                (mLauncher.isAllAppsSearchOverridden() && stateIsNormalHidden);
-        float finalSearchBarAlpha = showSearchBar ? 1f : 0f;
-        float finalWorkspaceTranslationY = stateIsOverview || stateIsOverviewHidden ?
+        float finalBackgroundAlpha = (states.stateIsSpringLoaded || states.stateIsOverview) ?
+                1.0f : 0f;
+        float finalHotseatAndPageIndicatorAlpha = (states.stateIsNormal || states.stateIsSpringLoaded) ?
+                1f : 0f;
+        float finalOverviewPanelAlpha = states.stateIsOverview ? 1f : 0f;
+        float finalWorkspaceTranslationY = states.stateIsOverview || states.stateIsOverviewHidden ?
                 mWorkspace.getOverviewModeTranslationY() : 0;
 
         final int childCount = mWorkspace.getChildCount();
@@ -238,29 +294,20 @@
 
         mNewScale = 1.0f;
 
-        if (oldStateIsOverview) {
+        if (states.oldStateIsOverview) {
             mWorkspace.disableFreeScroll();
-        } else if (stateIsOverview) {
+        } else if (states.stateIsOverview) {
             mWorkspace.enableFreeScroll();
         }
 
-        if (!stateIsNormal) {
-            if (stateIsSpringLoaded) {
+        if (!states.stateIsNormal) {
+            if (states.stateIsSpringLoaded) {
                 mNewScale = mSpringLoadedShrinkFactor;
-            } else if (stateIsOverview || stateIsOverviewHidden) {
+            } else if (states.stateIsOverview || states.stateIsOverviewHidden) {
                 mNewScale = mOverviewModeShrinkFactor;
             }
         }
 
-        final int duration;
-        if (workspaceToAllApps || overviewToAllApps) {
-            duration = mAllAppsTransitionTime;
-        } else if (workspaceToOverview || overviewToWorkspace) {
-            duration = mOverviewTransitionTime;
-        } else {
-            duration = mOverlayTransitionTime;
-        }
-
         if (toPage == SCROLL_TO_CURRENT_PAGE) {
             toPage = mWorkspace.getPageNearestToCenterOfScreen();
         }
@@ -271,9 +318,9 @@
             boolean isCurrentPage = (i == toPage);
             float initialAlpha = cl.getShortcutsAndWidgets().getAlpha();
             float finalAlpha;
-            if (stateIsNormalHidden || stateIsOverviewHidden) {
+            if (states.stateIsNormalHidden || states.stateIsOverviewHidden) {
                 finalAlpha = 0f;
-            } else if (stateIsNormal && mWorkspaceFadeInAdjacentScreens) {
+            } else if (states.stateIsNormal && mWorkspaceFadeInAdjacentScreens) {
                 finalAlpha = (i == toPage || i < customPageCount) ? 1f : 0f;
             } else {
                 finalAlpha = 1f;
@@ -282,8 +329,8 @@
             // If we are animating to/from the small state, then hide the side pages and fade the
             // current page in
             if (!mWorkspace.isSwitchingState()) {
-                if (workspaceToAllApps || allAppsToWorkspace) {
-                    if (allAppsToWorkspace && isCurrentPage) {
+                if (states.workspaceToAllApps || states.allAppsToWorkspace) {
+                    if (states.allAppsToWorkspace && isCurrentPage) {
                         initialAlpha = 0f;
                     } else if (!isCurrentPage) {
                         initialAlpha = finalAlpha = 0f;
@@ -303,7 +350,6 @@
             }
         }
 
-        final View searchBar = mLauncher.getOrCreateQsbBar();
         final ViewGroup overviewPanel = mLauncher.getOverviewPanel();
         final View hotseat = mLauncher.getHotseat();
         final View pageIndicator = mWorkspace.getPageIndicator();
@@ -345,7 +391,7 @@
                     }
                 }
             }
-            Animator pageIndicatorAlpha = null;
+            Animator pageIndicatorAlpha;
             if (pageIndicator != null) {
                 pageIndicatorAlpha = new LauncherViewPropertyAnimator(pageIndicator)
                         .alpha(finalHotseatAndPageIndicatorAlpha).withLayer();
@@ -380,11 +426,11 @@
                 overviewPanelAlpha.withLayer();
             }
 
-            if (workspaceToOverview) {
+            if (states.workspaceToOverview) {
                 pageIndicatorAlpha.setInterpolator(new DecelerateInterpolator(2));
                 hotseatAlpha.setInterpolator(new DecelerateInterpolator(2));
                 overviewPanelAlpha.setInterpolator(null);
-            } else if (overviewToWorkspace) {
+            } else if (states.overviewToWorkspace) {
                 pageIndicatorAlpha.setInterpolator(null);
                 hotseatAlpha.setInterpolator(null);
                 overviewPanelAlpha.setInterpolator(new DecelerateInterpolator(2));
@@ -394,26 +440,6 @@
             pageIndicatorAlpha.setDuration(duration);
             hotseatAlpha.setDuration(duration);
 
-            // TODO: This should really be coordinated with the SearchDropTargetBar, otherwise the
-            //       bar has no idea that it is hidden, and this has no idea what state the bar is
-            //       actually in.
-            if (searchBar != null) {
-                LauncherViewPropertyAnimator searchBarAlpha = new LauncherViewPropertyAnimator(searchBar)
-                    .alpha(finalSearchBarAlpha);
-                searchBarAlpha.addListener(new AlphaUpdateListener(searchBar, accessibilityEnabled));
-                searchBar.setLayerType(View.LAYER_TYPE_HARDWARE, null);
-                if (layerViews != null) {
-                    // If layerViews is not null, we add these views, and indicate that
-                    // the caller can manage layer state.
-                    layerViews.put(searchBar, LauncherStateTransitionAnimation.BUILD_AND_SET_LAYER);
-                } else {
-                    // Otherwise let the animator handle layer management.
-                    searchBarAlpha.withLayer();
-                }
-                searchBarAlpha.setDuration(duration);
-                mStateAnimator.play(searchBarAlpha);
-            }
-
             mStateAnimator.play(overviewPanelAlpha);
             mStateAnimator.play(hotseatAlpha);
             mStateAnimator.play(pageIndicatorAlpha);
@@ -437,10 +463,6 @@
                 pageIndicator.setAlpha(finalHotseatAndPageIndicatorAlpha);
                 AlphaUpdateListener.updateVisibility(pageIndicator, accessibilityEnabled);
             }
-            if (searchBar != null) {
-                searchBar.setAlpha(finalSearchBarAlpha);
-                AlphaUpdateListener.updateVisibility(searchBar, accessibilityEnabled);
-            }
             mWorkspace.updateCustomContentVisibility();
             mWorkspace.setScaleX(mNewScale);
             mWorkspace.setScaleY(mNewScale);
@@ -452,7 +474,7 @@
             }
         }
 
-        if (stateIsNormal) {
+        if (states.stateIsNormal) {
             animateBackgroundGradient(0f, animated);
         } else {
             animateBackgroundGradient(mWorkspaceScrimAlpha, animated);
@@ -460,16 +482,69 @@
     }
 
     /**
-     * Reinitializes the arrays that we need for the animations on each page.
+     * Coordinates with the workspace animation to animate the search bar.
+     *
+     * TODO: This should really be coordinated with the SearchDropTargetBar, otherwise the
+     *       bar has no idea that it is hidden, and this has no idea what state the bar is
+     *       actually in.
      */
-    private void reinitializeAnimationArrays() {
-        final int childCount = mWorkspace.getChildCount();
-        if (mLastChildCount == childCount) return;
+    private void animateSearchBar(TransitionStates states, boolean animated, int duration,
+            boolean hasOverlaySearchBar, final HashMap<View, Integer> layerViews,
+            final boolean accessibilityEnabled) {
 
-        mOldBackgroundAlphas = new float[childCount];
-        mOldAlphas = new float[childCount];
-        mNewBackgroundAlphas = new float[childCount];
-        mNewAlphas = new float[childCount];
+        // The search bar is only visible in the workspace
+        final View searchBar = mLauncher.getOrCreateQsbBar();
+        if (searchBar != null) {
+            final boolean searchBarWillBeShown = states.stateIsNormal;
+            final float finalSearchBarAlpha = searchBarWillBeShown ? 1f : 0f;
+            if (animated) {
+                if (hasOverlaySearchBar) {
+                    // If there is an overlay search bar, then we will coordinate with it.
+                    mStateAnimator.addListener(new AnimatorListenerAdapter() {
+                        @Override
+                        public void onAnimationStart(Animator animation) {
+                            // If we are transitioning to a visible search bar, show it immediately
+                            // and let the overlay search bar has faded out
+                            if (searchBarWillBeShown) {
+                                searchBar.setAlpha(finalSearchBarAlpha);
+                                AlphaUpdateListener.updateVisibility(searchBar, accessibilityEnabled);
+                            }
+                        }
+
+                        @Override
+                        public void onAnimationEnd(Animator animation) {
+                            // If we are transitioning to a hidden search bar, hide it only after
+                            // the overlay search bar has faded in
+                            if (!searchBarWillBeShown) {
+                                searchBar.setAlpha(finalSearchBarAlpha);
+                                AlphaUpdateListener.updateVisibility(searchBar, accessibilityEnabled);
+                            }
+                        }
+                    });
+                } else {
+                    // Otherwise, we can just do the normal animation
+                    LauncherViewPropertyAnimator searchBarAlpha =
+                            new LauncherViewPropertyAnimator(searchBar).alpha(finalSearchBarAlpha);
+                    searchBarAlpha.addListener(new AlphaUpdateListener(searchBar,
+                            accessibilityEnabled));
+                    searchBar.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+                    if (layerViews != null) {
+                        // If layerViews is not null, we add these views, and indicate that
+                        // the caller can manage layer state.
+                        layerViews.put(searchBar, LauncherStateTransitionAnimation.BUILD_AND_SET_LAYER);
+                    } else {
+                        // Otherwise let the animator handle layer management.
+                        searchBarAlpha.withLayer();
+                    }
+                    searchBarAlpha.setDuration(duration);
+                    mStateAnimator.play(searchBarAlpha);
+                }
+            } else {
+                // Set the search bar state immediately
+                searchBar.setAlpha(finalSearchBarAlpha);
+                AlphaUpdateListener.updateVisibility(searchBar, accessibilityEnabled);
+            }
+        }
     }
 
     /**
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 855a443..b300cae 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -20,17 +20,15 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.res.Resources;
-import android.graphics.Paint;
 import android.graphics.Point;
 import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.GradientDrawable;
 import android.graphics.drawable.InsetDrawable;
 import android.os.Build;
 import android.os.Bundle;
 import android.support.v7.widget.RecyclerView;
-import android.text.Editable;
-import android.text.TextWatcher;
+import android.text.Selection;
+import android.text.SpannableStringBuilder;
+import android.text.method.TextKeyListener;
 import android.util.AttributeSet;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
@@ -39,11 +37,8 @@
 import android.view.ViewConfiguration;
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputMethodManager;
 import android.widget.FrameLayout;
-import android.widget.TextView;
-
+import android.widget.LinearLayout;
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.BaseContainerView;
 import com.android.launcher3.BubbleTextView;
@@ -54,7 +49,6 @@
 import com.android.launcher3.DragSource;
 import com.android.launcher3.DropTarget;
 import com.android.launcher3.Folder;
-import com.android.launcher3.Insettable;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherTransitionable;
@@ -62,131 +56,24 @@
 import com.android.launcher3.Stats;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
-import com.android.launcher3.allapps.AppSearchManager.AppSearchResultCallback;
 import com.android.launcher3.util.Thunk;
 
 import java.util.ArrayList;
 import java.util.List;
 
 
-/**
- * Interface for controlling the header elevation in response to RecyclerView scroll.
- */
-interface HeaderElevationController {
-    void onScroll(int scrollY);
-    void updateBackgroundPadding(Drawable bg);
-    void disable();
-}
-
-/**
- * Implementation of the header elevation mechanism for pre-L devices.  It simulates elevation
- * by drawing a gradient under the header bar.
- */
-final class HeaderElevationControllerV16 implements HeaderElevationController {
-
-    private final View mShadow;
-    private final float mScrollToElevation;
-    private final Rect mTmpRect = new Rect();
-
-    public HeaderElevationControllerV16(View header) {
-        Resources res = header.getContext().getResources();
-        mScrollToElevation = res.getDimension(R.dimen.all_apps_header_scroll_to_elevation);
-
-        mShadow = new View(header.getContext());
-        mShadow.setBackground(new GradientDrawable(
-                GradientDrawable.Orientation.TOP_BOTTOM, new int[] {0x44000000, 0x00000000}));
-        mShadow.setAlpha(0);
-
-        FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
-                FrameLayout.LayoutParams.MATCH_PARENT,
-                res.getDimensionPixelSize(R.dimen.all_apps_header_shadow_height));
-        lp.topMargin = ((FrameLayout.LayoutParams) header.getLayoutParams()).height;
-
-        ((ViewGroup) header.getParent()).addView(mShadow, lp);
-    }
-
-    @Override
-    public void onScroll(int scrollY) {
-        float elevationPct = (float) Math.min(scrollY, mScrollToElevation) /
-                mScrollToElevation;
-        mShadow.setAlpha(elevationPct);
-    }
-
-    @Override
-    public void updateBackgroundPadding(Drawable bg) {
-        bg.getPadding(mTmpRect);
-        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mShadow.getLayoutParams();
-        lp.leftMargin = mTmpRect.left;
-        lp.rightMargin = mTmpRect.right;
-        mShadow.requestLayout();
-    }
-
-    @Override
-    public void disable() {
-        ViewGroup parent = (ViewGroup) mShadow.getParent();
-        if (parent != null) {
-            parent.removeView(mShadow);
-        }
-    }
-}
-
-/**
- * Implementation of the header elevation mechanism for L+ devices, which makes use of the native
- * view elevation.
- */
-@TargetApi(Build.VERSION_CODES.LOLLIPOP)
-final class HeaderElevationControllerVL implements HeaderElevationController {
-
-    private final View mHeader;
-    private final float mMaxElevation;
-    private final float mScrollToElevation;
-
-    public HeaderElevationControllerVL(View header) {
-        mHeader = header;
-
-        Resources res = header.getContext().getResources();
-        mMaxElevation = res.getDimension(R.dimen.all_apps_header_max_elevation);
-        mScrollToElevation = res.getDimension(R.dimen.all_apps_header_scroll_to_elevation);
-    }
-
-    @Override
-    public void onScroll(int scrollY) {
-        float elevationPct = (float) Math.min(scrollY, mScrollToElevation) /
-                mScrollToElevation;
-        float newElevation = mMaxElevation * elevationPct;
-        if (Float.compare(mHeader.getElevation(), newElevation) != 0) {
-            mHeader.setElevation(newElevation);
-        }
-    }
-
-    @Override
-    public void updateBackgroundPadding(Drawable bg) {
-        // Do nothing, the background padding on the header view is already applied
-    }
-
-    @Override
-    public void disable() { }
-}
 
 /**
  * The all apps view container.
  */
-public class AllAppsContainerView extends BaseContainerView implements DragSource, Insettable,
-        TextWatcher, TextView.OnEditorActionListener, LauncherTransitionable,
-        AlphabeticalAppsList.AdapterChangedCallback, AllAppsGridAdapter.PredictionBarSpacerCallbacks,
-        View.OnTouchListener, View.OnClickListener, View.OnLongClickListener,
-        ViewTreeObserver.OnPreDrawListener, AppSearchResultCallback, Stats.LaunchSourceProvider {
+public class AllAppsContainerView extends BaseContainerView implements DragSource,
+        LauncherTransitionable, AlphabeticalAppsList.AdapterChangedCallback,
+        AllAppsGridAdapter.PredictionBarSpacerCallbacks, View.OnTouchListener,
+        View.OnLongClickListener, ViewTreeObserver.OnPreDrawListener,
+        AllAppsSearchBarController.Callbacks, Stats.LaunchSourceProvider {
 
     public static final boolean GRID_MERGE_SECTIONS = true;
 
-    private static final boolean ALLOW_SINGLE_APP_LAUNCH = true;
-    private static final boolean DYNAMIC_HEADER_ELEVATION = true;
-    private static final boolean DISMISS_SEARCH_ON_BACK = true;
-
-    private static final int FADE_IN_DURATION = 175;
-    private static final int FADE_OUT_DURATION = 100;
-    private static final int SEARCH_TRANSLATION_X_DP = 18;
-
     @Thunk Launcher mLauncher;
     @Thunk AlphabeticalAppsList mApps;
     private LayoutInflater mLayoutInflater;
@@ -194,16 +81,14 @@
     private RecyclerView.LayoutManager mLayoutManager;
     private RecyclerView.ItemDecoration mItemDecoration;
 
-    @Thunk FrameLayout mContentView;
+    @Thunk View mContent;
+    @Thunk View mContainerView;
+    @Thunk View mRevealView;
     @Thunk AllAppsRecyclerView mAppsRecyclerView;
     @Thunk ViewGroup mPredictionBarView;
-    private View mHeaderView;
-    @Thunk View mSearchBarContainerView;
-    private View mSearchButtonView;
-    private View mDismissSearchButtonView;
-    @Thunk AllAppsSearchEditView mSearchBarEditView;
-
-    private HeaderElevationController mElevationController;
+    @Thunk AllAppsSearchBarController mSearchBarController;
+    private ViewGroup mSearchBarContainerView;
+    private View mSearchBarView;
 
     private int mNumAppsPerRow;
     private int mNumPredictedAppsPerRow;
@@ -213,18 +98,16 @@
     private final Point mIconLastTouchPos = new Point();
     // This coordinate is used to proxy click and long-click events to the prediction bar icons
     private final Point mPredictionIconTouchDownPos = new Point();
-    private int mContentMarginStart;
     // Normal container insets
-    private int mContainerInset;
     private int mPredictionBarHeight;
     private int mLastRecyclerViewScrollPos = -1;
     @Thunk boolean mFocusPredictionBarOnFirstBind;
 
+    private SpannableStringBuilder mSearchQueryBuilder = null;
+
     private CheckLongPressHelper mPredictionIconCheckForLongPress;
     private View mPredictionIconUnderTouch;
 
-    private AppSearchManager mSearchManager;
-
     public AllAppsContainerView(Context context) {
         this(context, null);
     }
@@ -238,30 +121,24 @@
         Resources res = context.getResources();
 
         mLauncher = (Launcher) context;
+        mLayoutInflater = LayoutInflater.from(context);
         DeviceProfile grid = mLauncher.getDeviceProfile();
-
-        mContainerInset = res.getDimensionPixelSize(R.dimen.all_apps_container_inset);
         mPredictionBarHeight = (int) (grid.allAppsIconSizePx + grid.iconDrawablePaddingOriginalPx +
                 Utilities.calculateTextHeight(grid.allAppsIconTextSizePx) +
                 2 * res.getDimensionPixelSize(R.dimen.all_apps_icon_top_bottom_padding) +
-                res.getDimensionPixelSize(R.dimen.all_apps_prediction_bar_bottom_padding));
+                2 * res.getDimensionPixelSize(R.dimen.all_apps_prediction_bar_top_bottom_padding));
 
-        mLayoutInflater = LayoutInflater.from(context);
-
-        mNumAppsPerRow = grid.allAppsNumCols;
-        mNumPredictedAppsPerRow = grid.allAppsNumPredictiveCols;
-        mApps = new AlphabeticalAppsList(context, mNumAppsPerRow, mNumPredictedAppsPerRow);
+        mApps = new AlphabeticalAppsList(context);
         mApps.setAdapterChangedCallback(this);
-        mAdapter = new AllAppsGridAdapter(context, mApps, mNumAppsPerRow, this, this, mLauncher, this);
+        mAdapter = new AllAppsGridAdapter(context, mApps, this, this, mLauncher, this);
         mAdapter.setEmptySearchText(res.getString(R.string.all_apps_loading_message));
-        mAdapter.setNumAppsPerRow(mNumAppsPerRow);
         mAdapter.setPredictionRowHeight(mPredictionBarHeight);
+        mApps.setAdapter(mAdapter);
         mLayoutManager = mAdapter.getLayoutManager();
         mItemDecoration = mAdapter.getItemDecoration();
-        mContentMarginStart = mAdapter.getContentMarginStart();
 
-        mApps.setAdapter(mAdapter);
-        mSearchManager = mApps.newSimpleAppSearchManager();
+        mSearchQueryBuilder = new SpannableStringBuilder();
+        Selection.setSelection(mSearchQueryBuilder, 0);
     }
 
     /**
@@ -285,11 +162,6 @@
         mApps.addApps(apps);
     }
 
-    public void setSearchManager(AppSearchManager searchManager) {
-        mSearchManager.cancel(true);
-        mSearchManager = searchManager;
-    }
-
     /**
      * Updates existing apps in the list
      */
@@ -305,13 +177,23 @@
     }
 
     /**
-     * Hides the header bar
+     * Sets the search bar that shows above the a-z list.
      */
-    public void hideHeaderBar() {
-        mHeaderView.setVisibility(View.GONE);
-        mElevationController.disable();
-        onUpdateBackgrounds();
-        onUpdatePaddings();
+    public void setSearchBarController(AllAppsSearchBarController searchController) {
+        if (mSearchBarController != null) {
+            throw new RuntimeException("Expected search bar controller to only be set once");
+        }
+        mSearchBarController = searchController;
+        mSearchBarController.initialize(mApps, this);
+
+        // Add the new search view to the layout
+        View searchBarView = searchController.getView(mSearchBarContainerView);
+        mSearchBarContainerView.addView(searchBarView);
+        mSearchBarContainerView.setVisibility(View.VISIBLE);
+        mSearchBarView = searchBarView;
+        setHasSearchBar();
+
+        updateBackgroundAndPaddings();
     }
 
     /**
@@ -325,28 +207,43 @@
      * Returns the content view used for the launcher transitions.
      */
     public View getContentView() {
-        return mContentView;
+        return mContainerView;
+    }
+
+    /**
+     * Returns the all apps search view.
+     */
+    public View getSearchBarView() {
+        return mSearchBarView;
     }
 
     /**
      * Returns the reveal view used for the launcher transitions.
      */
     public View getRevealView() {
-        return findViewById(R.id.apps_view_transition_overlay);
+        return mRevealView;
+    }
+
+    /**
+     * Returns an new instance of the default app search controller.
+     */
+    public AllAppsSearchBarController newDefaultAppSearchController() {
+        return new DefaultAppSearchController(getContext(), this, mAppsRecyclerView);
     }
 
     @Override
     protected void onFinishInflate() {
+        super.onFinishInflate();
         boolean isRtl = Utilities.isRtl(getResources());
         mAdapter.setRtl(isRtl);
+        mContent = findViewById(R.id.content);
 
-        // Work around the search box getting first focus and showing the cursor by
-        // proxying the focus from the content view to the recycler view directly
-        mContentView = (FrameLayout) findViewById(R.id.apps_list);
-        mContentView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
+        // This is a focus listener that proxies focus from a view into the list view.  This is to
+        // work around the search box from getting first focus and showing the cursor.
+        View.OnFocusChangeListener focusProxyListener = new View.OnFocusChangeListener() {
             @Override
             public void onFocusChange(View v, boolean hasFocus) {
-                if (v == mContentView && hasFocus) {
+                if (hasFocus) {
                     if (!mApps.getPredictedApps().isEmpty()) {
                         // If the prediction bar is going to be bound, then defer focusing until
                         // it is first bound
@@ -360,52 +257,16 @@
                     }
                 }
             }
-        });
+        };
+        mSearchBarContainerView = (ViewGroup) findViewById(R.id.search_box_container);
+        mSearchBarContainerView.setOnFocusChangeListener(focusProxyListener);
+        mContainerView = findViewById(R.id.all_apps_container);
+        mContainerView.setOnFocusChangeListener(focusProxyListener);
+        mRevealView = findViewById(R.id.all_apps_reveal);
 
-        // Fix the header view elevation if not dynamically calculating it
-        mHeaderView = findViewById(R.id.header);
-        mHeaderView.setOnClickListener(this);
-
-        mElevationController = Utilities.isLmpOrAbove() ?
-                new HeaderElevationControllerVL(mHeaderView) :
-                    new HeaderElevationControllerV16(mHeaderView);
-        if (!DYNAMIC_HEADER_ELEVATION) {
-            mElevationController.onScroll(getResources()
-                    .getDimensionPixelSize(R.dimen.all_apps_header_scroll_to_elevation));
-        }
-
-        // Fix the prediction bar size
-        mPredictionBarView = (ViewGroup) findViewById(R.id.prediction_bar);
-        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mPredictionBarView.getLayoutParams();
-        lp.height = mPredictionBarHeight;
-
-        mSearchButtonView = mHeaderView.findViewById(R.id.search_button);
-        mSearchBarContainerView = findViewById(R.id.app_search_container);
-        mDismissSearchButtonView = mSearchBarContainerView.findViewById(R.id.dismiss_search_button);
-        mDismissSearchButtonView.setOnClickListener(this);
-        mSearchBarEditView = (AllAppsSearchEditView) findViewById(R.id.apps_search_box);
-        if (mSearchBarEditView != null) {
-            mSearchBarEditView.addTextChangedListener(this);
-            mSearchBarEditView.setOnEditorActionListener(this);
-            if (DISMISS_SEARCH_ON_BACK) {
-                mSearchBarEditView.setOnBackKeyListener(
-                        new AllAppsSearchEditView.OnBackKeyListener() {
-                            @Override
-                            public void onBackKey() {
-                                // Only hide the search field if there is no query, or if there
-                                // are no filtered results
-                                String query = Utilities.trim(
-                                        mSearchBarEditView.getEditableText().toString());
-                                if (query.isEmpty() || mApps.hasNoFilteredResults()) {
-                                    hideSearchField(true, true);
-                                }
-                            }
-                        });
-            }
-        }
-        mAppsRecyclerView = (AllAppsRecyclerView) findViewById(R.id.apps_list_view);
+        // Load the all apps recycler view
+        mAppsRecyclerView = (AllAppsRecyclerView) findViewById(R.id.collection);
         mAppsRecyclerView.setApps(mApps);
-        mAppsRecyclerView.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow);
         mAppsRecyclerView.setPredictionBarHeight(mPredictionBarHeight);
         mAppsRecyclerView.setLayoutManager(mLayoutManager);
         mAppsRecyclerView.setAdapter(mAdapter);
@@ -413,8 +274,18 @@
         if (mItemDecoration != null) {
             mAppsRecyclerView.addItemDecoration(mItemDecoration);
         }
-        onUpdateBackgrounds();
-        onUpdatePaddings();
+
+        // Fix the prediction bar height
+        mPredictionBarView = (ViewGroup) findViewById(R.id.prediction_bar);
+        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mPredictionBarView.getLayoutParams();
+        lp.height = mPredictionBarHeight;
+
+        updateBackgroundAndPaddings();
+    }
+
+    @Override
+    public void onBoundsChanged(Rect newBounds) {
+        mLauncher.updateOverlayBounds(newBounds);
     }
 
     @Override
@@ -422,6 +293,12 @@
         updatePredictionBarVisibility();
 
         List<AppInfo> predictedApps = mApps.getPredictedApps();
+
+        // Remove extra prediction icons
+        while (mPredictionBarView.getChildCount() > mNumPredictedAppsPerRow) {
+            mPredictionBarView.removeViewAt(mPredictionBarView.getChildCount() - 1);
+        }
+
         int childCount = mPredictionBarView.getChildCount();
         for (int i = 0; i < mNumPredictedAppsPerRow; i++) {
             BubbleTextView icon;
@@ -455,98 +332,114 @@
     }
 
     @Override
-    protected void onFixedBoundsUpdated() {
-        // Update the number of items in the grid
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        // Update the number of items in the grid before we measure the view
+        int availableWidth = !mContentBounds.isEmpty() ? mContentBounds.width() :
+                MeasureSpec.getSize(widthMeasureSpec);
         DeviceProfile grid = mLauncher.getDeviceProfile();
-        if (grid.updateAppsViewNumCols(getContext().getResources(), mFixedBounds.width())) {
+        grid.updateAppsViewNumCols(getResources(), availableWidth);
+        if (mNumAppsPerRow != grid.allAppsNumCols ||
+                mNumPredictedAppsPerRow != grid.allAppsNumPredictiveCols) {
             mNumAppsPerRow = grid.allAppsNumCols;
             mNumPredictedAppsPerRow = grid.allAppsNumPredictiveCols;
             mAppsRecyclerView.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow);
             mAdapter.setNumAppsPerRow(mNumAppsPerRow);
             mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow);
         }
+
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
     }
 
     /**
-     * Update the padding of the Apps view and children.  To ensure that the RecyclerView has the
-     * full width to handle touches right to the edge of the screen, we only apply the top and
-     * bottom padding to the AppsContainerView and then the left/right padding on the RecyclerView
-     * itself.  In particular, the left/right padding is applied to the background of the view,
-     * and then additionally inset by the start margin.
+     * Update the background and padding of the Apps view and children.  Instead of insetting the
+     * container view, we inset the background and padding of the recycler view to allow for the
+     * recycler view to handle touch events (for fast scrolling) all the way to the edge.
      */
     @Override
-    protected void onUpdatePaddings() {
+    protected void onUpdateBackgroundAndPaddings(Rect searchBarBounds, Rect padding) {
         boolean isRtl = Utilities.isRtl(getResources());
-        boolean hasSearchBar = (mSearchBarEditView != null) &&
-                (mSearchBarEditView.getVisibility() == View.VISIBLE);
 
-        // Set the background on the container, but let the recyclerView extend the full screen,
-        // so that the fast-scroller works on the edge as well.
-        mContentView.setPadding(0, 0, 0, 0);
-
-        if (mFixedBounds.isEmpty()) {
-            // If there are no fixed bounds, then use the default padding and insets
-            setPadding(mInsets.left, mContainerInset + mInsets.top, mInsets.right,
-                    mContainerInset + mInsets.bottom);
-        } else {
-            // If there are fixed bounds, then we update the padding to reflect the fixed bounds.
-            setPadding(mFixedBounds.left, mFixedBounds.top, getMeasuredWidth() - mFixedBounds.right,
-                    mFixedBounds.bottom);
-        }
-
-        // Update the apps recycler view, inset it by the container inset as well
-        DeviceProfile grid = mLauncher.getDeviceProfile();
-        int startMargin = grid.isPhone ? mContentMarginStart : 0;
-        int inset = mFixedBounds.isEmpty() ? mContainerInset : mFixedBoundsContainerInset;
-        if (isRtl) {
-            mAppsRecyclerView.setPadding(inset + mAppsRecyclerView.getScrollbarWidth(), 0,
-                    inset + startMargin, 0);
-        } else {
-            mAppsRecyclerView.setPadding(inset + startMargin, 0,
-                    inset + mAppsRecyclerView.getScrollbarWidth(), 0);
-        }
-
-        // Update the header bar
-        if (hasSearchBar) {
-            FrameLayout.LayoutParams lp =
-                    (FrameLayout.LayoutParams) mHeaderView.getLayoutParams();
-            lp.leftMargin = lp.rightMargin = inset;
-            mHeaderView.requestLayout();
-        }
-
-        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mPredictionBarView.getLayoutParams();
-        lp.leftMargin = inset + mAppsRecyclerView.getScrollbarWidth();
-        lp.rightMargin = inset + mAppsRecyclerView.getScrollbarWidth();
-        mPredictionBarView.requestLayout();
-    }
-
-    /**
-     * Update the background of the Apps view and children.
-     */
-    @Override
-    protected void onUpdateBackgrounds() {
-        int inset = mFixedBounds.isEmpty() ? mContainerInset : mFixedBoundsContainerInset;
-
-        // Update the background of the reveal view and list to be inset with the fixed bound
-        // insets instead of the default insets
         // TODO: Use quantum_panel instead of quantum_panel_shape.
         InsetDrawable background = new InsetDrawable(
-                getContext().getResources().getDrawable(R.drawable.quantum_panel_shape),
-                inset, 0, inset, 0);
-        mContentView.setBackground(background);
-        mAppsRecyclerView.updateBackgroundPadding(background);
-        mAdapter.updateBackgroundPadding(background);
-        mElevationController.updateBackgroundPadding(background);
-        getRevealView().setBackground(background.getConstantState().newDrawable());
+                getResources().getDrawable(R.drawable.quantum_panel_shape), padding.left, 0,
+                padding.right, 0);
+        mContainerView.setBackground(background);
+        mRevealView.setBackground(background.getConstantState().newDrawable());
+        mAppsRecyclerView.updateBackgroundPadding(padding);
+        mAdapter.updateBackgroundPadding(padding);
+
+        // Hack: We are going to let the recycler view take the full width, so reset the padding on
+        // the container to zero after setting the background and apply the top-bottom padding to
+        // the content view instead so that the launcher transition clips correctly.
+        mContent.setPadding(0, padding.top, 0, padding.bottom);
+        mContainerView.setPadding(0, 0, 0, 0);
+
+        // Pad the recycler view by the background padding plus the start margin (for the section
+        // names)
+        DeviceProfile grid = mLauncher.getDeviceProfile();
+        int startMargin = grid.isPhone ? getResources().getDimensionPixelSize(
+                R.dimen.all_apps_grid_view_start_margin) : mAppsRecyclerView.getScrollbarWidth();
+        if (isRtl) {
+            mAppsRecyclerView.setPadding(padding.left + mAppsRecyclerView.getScrollbarWidth(), 0,
+                    padding.right + startMargin, 0);
+        } else {
+            mAppsRecyclerView.setPadding(padding.left + startMargin, 0,
+                    padding.right + mAppsRecyclerView.getScrollbarWidth(), 0);
+        }
+
+        // Inset the search bar to fit its bounds above the container
+        if (mSearchBarView != null) {
+            Rect backgroundPadding = new Rect();
+            if (mSearchBarView.getBackground() != null) {
+                mSearchBarView.getBackground().getPadding(backgroundPadding);
+            }
+            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
+                    mSearchBarContainerView.getLayoutParams();
+            lp.leftMargin = searchBarBounds.left - backgroundPadding.left;
+            lp.topMargin = searchBarBounds.top - backgroundPadding.top;
+            lp.rightMargin = (getMeasuredWidth() - searchBarBounds.right) - backgroundPadding.right;
+            mSearchBarContainerView.requestLayout();
+        }
+
+        // Update the prediction bar insets as well
+        mPredictionBarView = (ViewGroup) findViewById(R.id.prediction_bar);
+        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mPredictionBarView.getLayoutParams();
+        lp.leftMargin = padding.left + mAppsRecyclerView.getScrollbarWidth();
+        lp.rightMargin = padding.right + mAppsRecyclerView.getScrollbarWidth();
+        mPredictionBarView.requestLayout();
     }
 
     @Override
     public boolean onPreDraw() {
-        synchronizeToRecyclerViewScrollPosition(mAppsRecyclerView.getScrollPosition());
+        if (mNumAppsPerRow > 0) {
+            // Update the position of the prediction bar to match the scroll of the all apps list
+            synchronizeToRecyclerViewScrollPosition(mAppsRecyclerView.getScrollPosition());
+        }
         return true;
     }
 
     @Override
+    public boolean dispatchKeyEvent(KeyEvent event) {
+        // Determine if the key event was actual text, if so, focus the search bar and then dispatch
+        // the key normally so that it can process this key event
+        if (!mSearchBarController.isSearchFieldFocused() &&
+                event.getAction() == KeyEvent.ACTION_DOWN) {
+            final int unicodeChar = event.getUnicodeChar();
+            final boolean isKeyNotWhitespace = unicodeChar > 0 &&
+                    !Character.isWhitespace(unicodeChar) && !Character.isSpaceChar(unicodeChar);
+            if (isKeyNotWhitespace) {
+                boolean gotKey = TextKeyListener.getInstance().onKeyDown(this, mSearchQueryBuilder,
+                        event.getKeyCode(), event);
+                if (gotKey && mSearchQueryBuilder.length() > 0) {
+                    mSearchBarController.focusSearchField();
+                }
+            }
+        }
+
+        return super.dispatchKeyEvent(event);
+    }
+
+    @Override
     public boolean onInterceptTouchEvent(MotionEvent ev) {
         return handleTouchEvent(ev);
     }
@@ -570,15 +463,6 @@
     }
 
     @Override
-    public void onClick(View v) {
-        if (v == mHeaderView) {
-            showSearchField();
-        } else if (v == mDismissSearchButtonView) {
-            hideSearchField(true, true);
-        }
-    }
-
-    @Override
     public boolean onLongClick(View v) {
         // Return early if this is not initiated from a touch
         if (!v.isInTouchMode()) return false;
@@ -661,70 +545,11 @@
     }
 
     @Override
-    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
-        // Do nothing
-    }
-
-    @Override
-    public void onTextChanged(CharSequence s, int start, int before, int count) {
-        // Do nothing
-    }
-
-    @Override
-    public void afterTextChanged(final Editable s) {
-        String queryText = s.toString();
-        if (queryText.isEmpty()) {
-            mSearchManager.cancel(true);
-            mApps.setOrderedFilter(null);
-        } else {
-            String formatStr = getResources().getString(R.string.all_apps_no_search_results);
-            mAdapter.setEmptySearchText(String.format(formatStr, queryText));
-
-            mSearchManager.cancel(false);
-            mSearchManager.doSearch(queryText, this);
-        }
-        scrollToTop();
-    }
-
-    @Override
-    public void onSearchResult(ArrayList<ComponentName> apps) {
-        if (apps != null) {
-            mApps.setOrderedFilter(apps);
-        }
-    }
-
-    @Override
-    public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
-        if (ALLOW_SINGLE_APP_LAUNCH && actionId == EditorInfo.IME_ACTION_DONE) {
-            // Skip the quick-launch if there isn't exactly one item
-            if (mApps.getSize() != 1) {
-                return false;
-            }
-
-            List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
-            for (int i = 0; i < items.size(); i++) {
-                AlphabeticalAppsList.AdapterItem item = items.get(i);
-                if (item.viewType == AllAppsGridAdapter.ICON_VIEW_TYPE) {
-                    mAppsRecyclerView.getChildAt(i).performClick();
-                    getInputMethodManager().hideSoftInputFromWindow(getWindowToken(), 0);
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
-    @Override
     public void onAdapterItemsChanged() {
         updatePredictionBarVisibility();
     }
 
     @Override
-    public View getContent() {
-        return null;
-    }
-
-    @Override
     public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) {
         // Register for a pre-draw listener to synchronize the recycler view scroll to other views
         // in this container
@@ -745,14 +570,12 @@
 
     @Override
     public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
-        if (mSearchBarEditView != null) {
-            if (toWorkspace) {
-                hideSearchField(false, false);
-            }
-        }
         if (toWorkspace) {
             getViewTreeObserver().removeOnPreDrawListener(this);
             mLastRecyclerViewScrollPos = -1;
+
+            // Reset the search bar after transitioning home
+            mSearchBarController.reset();
         }
     }
 
@@ -763,9 +586,6 @@
     private void synchronizeToRecyclerViewScrollPosition(int scrollY) {
         if (mLastRecyclerViewScrollPos != scrollY) {
             mLastRecyclerViewScrollPos = scrollY;
-            if (DYNAMIC_HEADER_ELEVATION) {
-                mElevationController.onScroll(scrollY);
-            }
 
             // Scroll the prediction bar with the contents of the recycler view
             mPredictionBarView.setTranslationY(-scrollY + mAppsRecyclerView.getPaddingTop());
@@ -806,9 +626,9 @@
                     }
                 }
 
-                if (!mFixedBounds.isEmpty()) {
+                if (!mContentBounds.isEmpty()) {
                     // Outset the fixed bounds and check if the touch is outside all apps
-                    Rect tmpRect = new Rect(mFixedBounds);
+                    Rect tmpRect = new Rect(mContentBounds);
                     tmpRect.inset(-grid.allAppsIconSizePx / 2, 0);
                     if (ev.getX() < tmpRect.left || ev.getX() > tmpRect.right) {
                         mBoundsCheckLastTouchDownPos.set(x, y);
@@ -875,6 +695,29 @@
     }
 
     @Override
+    public void onSearchResult(String query, ArrayList<ComponentName> apps) {
+        if (apps != null) {
+            if (apps.isEmpty()) {
+                String formatStr = getResources().getString(R.string.all_apps_no_search_results);
+                mAdapter.setEmptySearchText(String.format(formatStr, query));
+            } else {
+                mAppsRecyclerView.scrollToTop();
+            }
+            mApps.setOrderedFilter(apps);
+        }
+    }
+
+    @Override
+    public void clearSearchResult() {
+        mApps.setOrderedFilter(null);
+
+        // Clear the search query
+        mSearchQueryBuilder.clear();
+        mSearchQueryBuilder.clearSpans();
+        Selection.setSelection(mSearchQueryBuilder, 0);
+    }
+
+    @Override
     public void fillInLaunchSourceData(Bundle sourceData) {
         // Since the other cases are caught by the AllAppsRecyclerView LaunchSourceProvider, we just
         // handle the prediction bar icons here
@@ -889,11 +732,11 @@
     private View findPredictedAppAtCoordinate(int x, int y) {
         Rect hitRect = new Rect();
 
-        // Ensure we aren't hitting the search bar
+        // Ensure that are touching in the recycler view
         int[] coord = {x, y};
-        Utilities.mapCoordInSelfToDescendent(mHeaderView, this, coord);
-        mHeaderView.getHitRect(hitRect);
-        if (hitRect.contains(coord[0], coord[1])) {
+        Utilities.mapCoordInSelfToDescendent(mAppsRecyclerView, this, coord);
+        mAppsRecyclerView.getHitRect(hitRect);
+        if (!hitRect.contains(coord[0], coord[1])) {
             return null;
         }
 
@@ -915,94 +758,12 @@
     }
 
     /**
-     * Shows the search field.
-     */
-    private void showSearchField() {
-        mSearchManager.connect();
-
-        // Show the search bar and focus the search
-        final int translationX = Utilities.pxFromDp(SEARCH_TRANSLATION_X_DP,
-                getContext().getResources().getDisplayMetrics());
-        mSearchBarContainerView.setVisibility(View.VISIBLE);
-        mSearchBarContainerView.setAlpha(0f);
-        mSearchBarContainerView.setTranslationX(translationX);
-        mSearchBarContainerView.animate()
-                .alpha(1f)
-                .translationX(0)
-                .setDuration(FADE_IN_DURATION)
-                .withLayer()
-                .withEndAction(new Runnable() {
-                    @Override
-                    public void run() {
-                        mSearchBarEditView.requestFocus();
-                        getInputMethodManager().showSoftInput(mSearchBarEditView,
-                                InputMethodManager.SHOW_IMPLICIT);
-                    }
-                });
-        mSearchButtonView.animate()
-                .alpha(0f)
-                .translationX(-translationX)
-                .setDuration(FADE_OUT_DURATION)
-                .withLayer();
-    }
-
-    /**
-     * Hides the search field.
-     */
-    @Thunk void hideSearchField(boolean animated, final boolean returnFocusToRecyclerView) {
-        mSearchManager.cancel(true);
-
-        final boolean resetTextField = mSearchBarEditView.getText().toString().length() > 0;
-        final int translationX = Utilities.pxFromDp(SEARCH_TRANSLATION_X_DP,
-                getContext().getResources().getDisplayMetrics());
-        if (animated) {
-            // Hide the search bar and focus the recycler view
-            mSearchBarContainerView.animate()
-                    .alpha(0f)
-                    .translationX(0)
-                    .setDuration(FADE_IN_DURATION)
-                    .withLayer()
-                    .withEndAction(new Runnable() {
-                        @Override
-                        public void run() {
-                            mSearchBarContainerView.setVisibility(View.INVISIBLE);
-                            if (resetTextField) {
-                                mSearchBarEditView.setText("");
-                            }
-                            mApps.setOrderedFilter(null);
-                            if (returnFocusToRecyclerView) {
-                                mAppsRecyclerView.requestFocus();
-                            }
-                        }
-                    });
-            mSearchButtonView.setTranslationX(-translationX);
-            mSearchButtonView.animate()
-                    .alpha(1f)
-                    .translationX(0)
-                    .setDuration(FADE_OUT_DURATION)
-                    .withLayer();
-        } else {
-            mSearchBarContainerView.setVisibility(View.INVISIBLE);
-            if (resetTextField) {
-                mSearchBarEditView.setText("");
-            }
-            mApps.setOrderedFilter(null);
-            mSearchButtonView.setAlpha(1f);
-            mSearchButtonView.setTranslationX(0f);
-            if (returnFocusToRecyclerView) {
-                mAppsRecyclerView.requestFocus();
-            }
-        }
-        getInputMethodManager().hideSoftInputFromWindow(getWindowToken(), 0);
-    }
-
-    /**
      * Updates the visibility of the prediction bar.
      * @return whether the prediction bar is visible
      */
     private boolean updatePredictionBarVisibility() {
-        boolean showPredictionBar = !mApps.getPredictedApps().isEmpty() && (!mApps.hasFilter() ||
-                mSearchBarEditView.getEditableText().toString().isEmpty());
+        boolean showPredictionBar = !mApps.getPredictedApps().isEmpty() &&
+                (!mApps.hasFilter() || mSearchBarController.shouldShowPredictionBar());
         if (showPredictionBar) {
             mPredictionBarView.setVisibility(View.VISIBLE);
         } else if (!showPredictionBar) {
@@ -1010,11 +771,4 @@
         }
         return showPredictionBar;
     }
-
-    /**
-     * Returns an input method manager.
-     */
-    @Thunk InputMethodManager getInputMethodManager() {
-        return (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
-    }
 }
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index 4b8b2df..dc0d27c 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -109,6 +109,7 @@
      */
     public class GridItemDecoration extends RecyclerView.ItemDecoration {
 
+        private static final boolean DEBUG_SECTION_MARGIN = false;
         private static final boolean FADE_OUT_SECTIONS = false;
 
         private HashMap<String, PointF> mCachedSectionBounds = new HashMap<>();
@@ -121,10 +122,17 @@
 
         @Override
         public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
-            if (mApps.hasFilter()) {
+            if (mApps.hasFilter() || mAppsPerRow == 0) {
                 return;
             }
 
+            if (DEBUG_SECTION_MARGIN) {
+                Paint p = new Paint();
+                p.setColor(0x33ff0000);
+                c.drawRect(mBackgroundPadding.left, 0, mBackgroundPadding.left + mStartMargin,
+                        parent.getMeasuredHeight(), p);
+            }
+
             DeviceProfile grid = mLauncher.getDeviceProfile();
             List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
             boolean hasDrawnPredictedAppsDivider = false;
@@ -171,8 +179,8 @@
 
                         // Calculate where to draw the section
                         int sectionBaseline = (int) (viewTopOffset + sectionBounds.y);
-                        int x = mIsRtl ? parent.getWidth() - mPaddingStart - mStartMargin :
-                                mPaddingStart;
+                        int x = mIsRtl ? parent.getWidth() - mBackgroundPadding.left - mStartMargin :
+                                mBackgroundPadding.left;
                         x += (int) ((mStartMargin - sectionBounds.x) / 2f);
                         int y = child.getTop() + sectionBaseline;
 
@@ -301,23 +309,20 @@
     private String mEmptySearchText;
 
     // Section drawing
-    @Thunk int mPaddingStart;
     @Thunk int mStartMargin;
     @Thunk int mSectionHeaderOffset;
     @Thunk Paint mSectionTextPaint;
     @Thunk Paint mPredictedAppsDividerPaint;
 
-    public AllAppsGridAdapter(Context context, AlphabeticalAppsList apps, int appsPerRow,
+    public AllAppsGridAdapter(Context context, AlphabeticalAppsList apps,
             PredictionBarSpacerCallbacks pbCb, View.OnTouchListener touchListener,
             View.OnClickListener iconClickListener, View.OnLongClickListener iconLongClickListener) {
         Resources res = context.getResources();
         mHandler = new Handler();
         mApps = apps;
-        mAppsPerRow = appsPerRow;
         mPredictionBarCb = pbCb;
         mGridSizer = new GridSpanSizer();
-        mGridLayoutMgr = new GridLayoutManager(context, appsPerRow, GridLayoutManager.VERTICAL,
-                false);
+        mGridLayoutMgr = new GridLayoutManager(context, 1, GridLayoutManager.VERTICAL, false);
         mGridLayoutMgr.setSpanSizeLookup(mGridSizer);
         mItemDecoration = new GridItemDecoration(context);
         mLayoutInflater = LayoutInflater.from(context);
@@ -326,7 +331,6 @@
         mIconLongClickListener = iconLongClickListener;
         mStartMargin = res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin);
         mSectionHeaderOffset = res.getDimensionPixelSize(R.dimen.all_apps_grid_section_y_offset);
-        mPaddingStart = res.getDimensionPixelSize(R.dimen.all_apps_container_inset);
 
         mSectionTextPaint = new Paint();
         mSectionTextPaint.setTextSize(res.getDimensionPixelSize(
@@ -339,7 +343,7 @@
         mPredictedAppsDividerPaint.setColor(0x1E000000);
         mPredictedAppsDividerPaint.setAntiAlias(true);
         mPredictionBarBottomPadding =
-                res.getDimensionPixelSize(R.dimen.all_apps_prediction_bar_bottom_padding);
+                res.getDimensionPixelSize(R.dimen.all_apps_prediction_bar_top_bottom_padding);
     }
 
     /**
@@ -375,8 +379,8 @@
      * Notifies the adapter of the background padding so that it can draw things correctly in the
      * item decorator.
      */
-    public void updateBackgroundPadding(Drawable background) {
-        background.getPadding(mBackgroundPadding);
+    public void updateBackgroundPadding(Rect padding) {
+        mBackgroundPadding.set(padding);
     }
 
     /**
@@ -394,13 +398,6 @@
         return mItemDecoration;
     }
 
-    /**
-     * Returns the left padding for the recycler view.
-     */
-    public int getContentMarginStart() {
-        return mStartMargin;
-    }
-
     @Override
     public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
         switch (viewType) {
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index e1b5d91..25918ce 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -16,8 +16,6 @@
 package com.android.launcher3.allapps;
 
 import android.content.Context;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.support.v7.widget.LinearLayoutManager;
 import android.support.v7.widget.RecyclerView;
@@ -40,11 +38,7 @@
     private AlphabeticalAppsList mApps;
     private int mNumAppsPerRow;
     private int mNumPredictedAppsPerRow;
-
     private int mPredictionBarHeight;
-    private int mScrollbarMinHeight;
-
-    private Rect mBackgroundPadding = new Rect();
 
     private Launcher mLauncher;
 
@@ -89,10 +83,6 @@
         pool.setMaxRecycledViews(AllAppsGridAdapter.SECTION_BREAK_VIEW_TYPE, approxRows);
     }
 
-    public void updateBackgroundPadding(Drawable background) {
-        background.getPadding(mBackgroundPadding);
-    }
-
     /**
      * Sets the prediction bar height.
      */
@@ -124,6 +114,8 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
+
+        // Bind event handlers
         addOnItemTouchListener(this);
     }
 
@@ -227,8 +219,8 @@
     public void updateVerticalScrollbarBounds() {
         List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
 
-        // Skip early if there are no items.
-        if (items.isEmpty()) {
+        // Skip early if there are no items or we haven't been measured
+        if (items.isEmpty() || mNumAppsPerRow == 0) {
             verticalScrollbarBounds.setEmpty();
             return;
         }
@@ -242,8 +234,7 @@
             int height = getHeight() - getPaddingTop() - getPaddingBottom();
             int totalScrollHeight = rowCount * scrollPosState.rowHeight + predictionBarHeight;
             if (totalScrollHeight > height) {
-                int scrollbarHeight = Math.max(mScrollbarMinHeight,
-                        (int) (height / ((float) totalScrollHeight / height)));
+                int scrollbarHeight = (int) (height / ((float) totalScrollHeight / height));
 
                 // Calculate the position and size of the scroll bar
                 if (Utilities.isRtl(getResources())) {
@@ -277,8 +268,8 @@
         stateOut.rowTopOffset = -1;
         stateOut.rowHeight = -1;
 
-        // Return early if there are no items
-        if (items.isEmpty()) {
+        // Return early if there are no items or we haven't been measured
+        if (items.isEmpty() || mNumAppsPerRow == 0) {
             return;
         }
 
diff --git a/src/com/android/launcher3/allapps/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/AllAppsSearchBarController.java
new file mode 100644
index 0000000..3cacd9d
--- /dev/null
+++ b/src/com/android/launcher3/allapps/AllAppsSearchBarController.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.allapps;
+
+import android.content.ComponentName;
+import android.graphics.Rect;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+
+/**
+ * An interface to a search box that AllApps can command.
+ */
+public abstract class AllAppsSearchBarController {
+
+    protected AlphabeticalAppsList mApps;
+    protected Callbacks mCb;
+
+    /**
+     * Sets the references to the apps model and the search result callback.
+     */
+    public final void initialize(AlphabeticalAppsList apps, Callbacks cb) {
+        mApps = apps;
+        mCb = cb;
+        onInitialize();
+    }
+
+    /**
+     * To be overridden by subclasses.  This method will get called when the controller is set,
+     * before getView().
+     */
+    protected abstract void onInitialize();
+
+    /**
+     * Returns the search bar view.
+     * @param parent the parent to attach the search bar view to.
+     */
+    public abstract View getView(ViewGroup parent);
+
+    /**
+     * Focuses the search field to handle key events.
+     */
+    public abstract void focusSearchField();
+
+    /**
+     * Returns whether the search field is focused.
+     */
+    public abstract boolean isSearchFieldFocused();
+
+    /**
+     * Resets the search bar state.
+     */
+    public abstract void reset();
+
+    /**
+     * Returns whether the prediction bar should currently be visible depending on the state of
+     * the search bar.
+     */
+    public abstract boolean shouldShowPredictionBar();
+
+    /**
+     * Callback for getting search results.
+     */
+    public interface Callbacks {
+
+        /**
+         * Called when the bounds of the search bar has changed.
+         */
+        void onBoundsChanged(Rect newBounds);
+
+        /**
+         * Called when the search is complete.
+         *
+         * @param apps sorted list of matching components or null if in case of failure.
+         */
+        void onSearchResult(String query, ArrayList<ComponentName> apps);
+
+        /**
+         * Called when the search results should be cleared.
+         */
+        void clearSearchResult();
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index e284f77..a0cf5b6 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -235,11 +235,10 @@
     private int mNumAppsPerRow;
     private int mNumPredictedAppsPerRow;
 
-    public AlphabeticalAppsList(Context context, int numAppsPerRow, int numPredictedAppsPerRow) {
+    public AlphabeticalAppsList(Context context) {
         mLauncher = (Launcher) context;
         mIndexer = new AlphabeticIndexCompat(context);
         mAppNameComparator = new AppNameComparator(context);
-        setNumAppsPerRow(numAppsPerRow, numPredictedAppsPerRow);
     }
 
     /**
@@ -249,10 +248,6 @@
         mAdapterChangedCallback = cb;
     }
 
-    public SimpleAppSearchManagerImpl newSimpleAppSearchManager() {
-        return new SimpleAppSearchManagerImpl(mApps);
-    }
-
     /**
      * Sets the number of apps per row.  Used only for AppsContainerView.SECTIONED_GRID_COALESCED.
      */
@@ -269,7 +264,7 @@
         mNumAppsPerRow = numAppsPerRow;
         mNumPredictedAppsPerRow = numPredictedAppsPerRow;
 
-        onAppsUpdated();
+        updateAdapterItems();
     }
 
     /**
@@ -280,6 +275,13 @@
     }
 
     /**
+     * Returns all the apps.
+     */
+    public List<AppInfo> getApps() {
+        return mApps;
+    }
+
+    /**
      * Returns sections of all the current filtered applications.
      */
     public List<SectionInfo> getSections() {
@@ -597,6 +599,11 @@
      * Merges multiple sections to reduce visual raggedness.
      */
     private void mergeSections() {
+        // Ignore merging until we have a valid row size
+        if (mNumAppsPerRow == 0) {
+            return;
+        }
+
         // Go through each section and try and merge some of the sections
         if (AllAppsContainerView.GRID_MERGE_SECTIONS && !hasFilter()) {
             int sectionAppCount = 0;
diff --git a/src/com/android/launcher3/allapps/AppSearchManager.java b/src/com/android/launcher3/allapps/AppSearchManager.java
deleted file mode 100644
index b6aa223..0000000
--- a/src/com/android/launcher3/allapps/AppSearchManager.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.allapps;
-
-import android.content.ComponentName;
-
-import java.util.ArrayList;
-
-/**
- * Interface for handling app search.
- */
-public interface AppSearchManager {
-
-    /**
-     * Called when the search is about to be used. This method is optional for making a query but
-     * calling this appropriately can improve the initial response time.
-     */
-    void connect();
-
-    /**
-     * Cancels all pending search requests.
-     *
-     * @param interruptActiveRequests if true, any active requests which are already executing will
-     * be invalidated, and the corresponding results will not be sent. The client should usually
-     * set this to true, before beginning a new search session.
-     */
-    void cancel(boolean interruptActiveRequests);
-
-    /**
-     * Performs a search
-     */
-    void doSearch(String query, AppSearchResultCallback callback);
-
-    /**
-     * Callback for getting search results.
-     */
-    public interface AppSearchResultCallback {
-
-        /**
-         * Called when the search is complete.
-         *
-         * @param apps sorted list of matching components or null if in case of failure.
-         */
-        void onSearchResult(ArrayList<ComponentName> apps);
-    }
-}
diff --git a/src/com/android/launcher3/allapps/SimpleAppSearchManagerImpl.java b/src/com/android/launcher3/allapps/DefaultAppSearchAlgorithm.java
similarity index 83%
rename from src/com/android/launcher3/allapps/SimpleAppSearchManagerImpl.java
rename to src/com/android/launcher3/allapps/DefaultAppSearchAlgorithm.java
index e8a31b5..28854be 100644
--- a/src/com/android/launcher3/allapps/SimpleAppSearchManagerImpl.java
+++ b/src/com/android/launcher3/allapps/DefaultAppSearchAlgorithm.java
@@ -17,7 +17,6 @@
 
 import android.content.ComponentName;
 import android.os.Handler;
-
 import com.android.launcher3.AppInfo;
 
 import java.util.ArrayList;
@@ -25,39 +24,33 @@
 import java.util.regex.Pattern;
 
 /**
- * An {@link AppSearchManager} which does label matching on the UI thread.
+ * The default search implementation.
  */
-public class SimpleAppSearchManagerImpl implements AppSearchManager {
+public class DefaultAppSearchAlgorithm {
 
     private static final Pattern SPLIT_PATTERN = Pattern.compile("[\\s|\\p{javaSpaceChar}]+");
 
     private final List<AppInfo> mApps;
     private final Handler mResultHandler;
 
-    public SimpleAppSearchManagerImpl(List<AppInfo> apps) {
+    public DefaultAppSearchAlgorithm(List<AppInfo> apps) {
         mApps = apps;
         mResultHandler = new Handler();
     }
 
-    @Override
-    public void connect() {
-        // No op
-    }
-
-    @Override
     public void cancel(boolean interruptActiveRequests) {
         if (interruptActiveRequests) {
             mResultHandler.removeCallbacksAndMessages(null);
         }
     }
 
-    @Override
-    public void doSearch(String query, final AppSearchResultCallback callback) {
+    public void doSearch(final String query,
+            final AllAppsSearchBarController.Callbacks callback) {
         // Do an intersection of the words in the query and each title, and filter out all the
         // apps that don't match all of the words in the query.
         final String queryTextLower = query.toLowerCase();
         final String[] queryWords = SPLIT_PATTERN.split(queryTextLower);
-        final ArrayList<ComponentName> result = new ArrayList<ComponentName>();
+        final ArrayList<ComponentName> result = new ArrayList<>();
         int total = mApps.size();
 
         for (int i = 0; i < total; i++) {
@@ -70,7 +63,7 @@
 
             @Override
             public void run() {
-                callback.onSearchResult(result);
+                callback.onSearchResult(query, result);
             }
         });
     }
diff --git a/src/com/android/launcher3/allapps/DefaultAppSearchController.java b/src/com/android/launcher3/allapps/DefaultAppSearchController.java
new file mode 100644
index 0000000..1601c62
--- /dev/null
+++ b/src/com/android/launcher3/allapps/DefaultAppSearchController.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.allapps;
+
+import android.content.Context;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.TextView;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.util.Thunk;
+
+import java.util.List;
+
+
+/**
+ * The default search controller.
+ */
+final class DefaultAppSearchController extends AllAppsSearchBarController
+        implements TextWatcher, TextView.OnEditorActionListener, View.OnClickListener {
+
+    private static final boolean ALLOW_SINGLE_APP_LAUNCH = true;
+
+    private static final int FADE_IN_DURATION = 175;
+    private static final int FADE_OUT_DURATION = 100;
+    private static final int SEARCH_TRANSLATION_X_DP = 18;
+
+    private final Context mContext;
+    @Thunk final InputMethodManager mInputMethodManager;
+
+    private DefaultAppSearchAlgorithm mSearchManager;
+
+    private ViewGroup mContainerView;
+    private View mSearchView;
+    @Thunk View mSearchBarContainerView;
+    private View mSearchButtonView;
+    private View mDismissSearchButtonView;
+    @Thunk AllAppsSearchEditView mSearchBarEditView;
+    @Thunk AllAppsRecyclerView mAppsRecyclerView;
+    private Runnable mFocusRecyclerViewRunnable = new Runnable() {
+        @Override
+        public void run() {
+            mAppsRecyclerView.requestFocus();
+        }
+    };
+
+    public DefaultAppSearchController(Context context, ViewGroup containerView,
+            AllAppsRecyclerView appsRecyclerView) {
+        mContext = context;
+        mInputMethodManager = (InputMethodManager)
+                mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
+        mContainerView = containerView;
+        mAppsRecyclerView = appsRecyclerView;
+    }
+
+    @Override
+    public View getView(ViewGroup parent) {
+        LayoutInflater inflater = LayoutInflater.from(parent.getContext());
+        mSearchView = inflater.inflate(R.layout.all_apps_search_bar, parent, false);
+        mSearchView.setOnClickListener(this);
+
+        mSearchButtonView = mSearchView.findViewById(R.id.search_button);
+        mSearchBarContainerView = mSearchView.findViewById(R.id.search_container);
+        mDismissSearchButtonView = mSearchBarContainerView.findViewById(R.id.dismiss_search_button);
+        mDismissSearchButtonView.setOnClickListener(this);
+        mSearchBarEditView = (AllAppsSearchEditView)
+                mSearchBarContainerView.findViewById(R.id.search_box);
+        mSearchBarEditView.addTextChangedListener(this);
+        mSearchBarEditView.setOnEditorActionListener(this);
+        mSearchBarEditView.setOnBackKeyListener(
+                new AllAppsSearchEditView.OnBackKeyListener() {
+                    @Override
+                    public void onBackKey() {
+                        // Only hide the search field if there is no query, or if there
+                        // are no filtered results
+                        String query = Utilities.trim(
+                                mSearchBarEditView.getEditableText().toString());
+                        if (query.isEmpty() || mApps.hasNoFilteredResults()) {
+                            hideSearchField(true, mFocusRecyclerViewRunnable);
+                        }
+                    }
+                });
+        return mSearchView;
+    }
+
+    @Override
+    public void focusSearchField() {
+        mSearchBarEditView.requestFocus();
+        showSearchField();
+    }
+
+    @Override
+    public boolean isSearchFieldFocused() {
+        return mSearchBarEditView.isFocused();
+    }
+
+    @Override
+    protected void onInitialize() {
+        mSearchManager = new DefaultAppSearchAlgorithm(mApps.getApps());
+    }
+
+    @Override
+    public void reset() {
+        hideSearchField(false, null);
+    }
+
+    @Override
+    public boolean shouldShowPredictionBar() {
+        // Keep showing the prediction bar if the input query is empty
+        return mSearchBarEditView.getEditableText().toString().isEmpty();
+    }
+
+    @Override
+    public void onClick(View v) {
+        if (v == mSearchView) {
+            showSearchField();
+        } else if (v == mDismissSearchButtonView) {
+            hideSearchField(true, mFocusRecyclerViewRunnable);
+        }
+    }
+
+    @Override
+    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+        // Do nothing
+    }
+
+    @Override
+    public void onTextChanged(CharSequence s, int start, int before, int count) {
+        // Do nothing
+    }
+
+    @Override
+    public void afterTextChanged(final Editable s) {
+        String query = s.toString();
+        if (query.isEmpty()) {
+            mSearchManager.cancel(true);
+            mCb.clearSearchResult();
+        } else {
+            mSearchManager.cancel(false);
+            mSearchManager.doSearch(query, mCb);
+        }
+    }
+
+    @Override
+    public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+        // Skip if we disallow app-launch-on-enter
+        if (!ALLOW_SINGLE_APP_LAUNCH) {
+            return false;
+        }
+        // Skip if it's not the right action
+        if (actionId != EditorInfo.IME_ACTION_DONE) {
+            return false;
+        }
+        // Skip if there isn't exactly one item
+        if (mApps.getSize() != 1) {
+            return false;
+        }
+        // If there is exactly one icon, then quick-launch it
+        List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
+        for (int i = 0; i < items.size(); i++) {
+            AlphabeticalAppsList.AdapterItem item = items.get(i);
+            if (item.viewType == AllAppsGridAdapter.ICON_VIEW_TYPE) {
+                mAppsRecyclerView.getChildAt(i).performClick();
+                mInputMethodManager.hideSoftInputFromWindow(
+                        mContainerView.getWindowToken(), 0);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Focuses the search field.
+     */
+    private void showSearchField() {
+        // Show the search bar and focus the search
+        final int translationX = Utilities.pxFromDp(SEARCH_TRANSLATION_X_DP,
+                mContext.getResources().getDisplayMetrics());
+        mSearchBarContainerView.setVisibility(View.VISIBLE);
+        mSearchBarContainerView.setAlpha(0f);
+        mSearchBarContainerView.setTranslationX(translationX);
+        mSearchBarContainerView.animate()
+                .alpha(1f)
+                .translationX(0)
+                .setDuration(FADE_IN_DURATION)
+                .withLayer()
+                .withEndAction(new Runnable() {
+                    @Override
+                    public void run() {
+                        mSearchBarEditView.requestFocus();
+                        mInputMethodManager.showSoftInput(mSearchBarEditView,
+                                InputMethodManager.SHOW_IMPLICIT);
+                    }
+                });
+        mSearchButtonView.animate()
+                .alpha(0f)
+                .translationX(-translationX)
+                .setDuration(FADE_OUT_DURATION)
+                .withLayer();
+    }
+
+    /**
+     * Unfocuses the search field.
+     */
+    @Thunk void hideSearchField(boolean animated, final Runnable postAnimationRunnable) {
+        mSearchManager.cancel(true);
+
+        final boolean resetTextField = mSearchBarEditView.getText().toString().length() > 0;
+        final int translationX = Utilities.pxFromDp(SEARCH_TRANSLATION_X_DP,
+                mContext.getResources().getDisplayMetrics());
+        if (animated) {
+            // Hide the search bar and focus the recycler view
+            mSearchBarContainerView.animate()
+                    .alpha(0f)
+                    .translationX(0)
+                    .setDuration(FADE_IN_DURATION)
+                    .withLayer()
+                    .withEndAction(new Runnable() {
+                        @Override
+                        public void run() {
+                            mSearchBarContainerView.setVisibility(View.INVISIBLE);
+                            if (resetTextField) {
+                                mSearchBarEditView.setText("");
+                            }
+                            mCb.clearSearchResult();
+                            if (postAnimationRunnable != null) {
+                                postAnimationRunnable.run();
+                            }
+                        }
+                    });
+            mSearchButtonView.setTranslationX(-translationX);
+            mSearchButtonView.animate()
+                    .alpha(1f)
+                    .translationX(0)
+                    .setDuration(FADE_OUT_DURATION)
+                    .withLayer();
+        } else {
+            mSearchBarContainerView.setVisibility(View.INVISIBLE);
+            if (resetTextField) {
+                mSearchBarEditView.setText("");
+            }
+            mCb.clearSearchResult();
+            mSearchButtonView.setAlpha(1f);
+            mSearchButtonView.setTranslationX(0f);
+            if (postAnimationRunnable != null) {
+                postAnimationRunnable.run();
+            }
+        }
+        mInputMethodManager.hideSoftInputFromWindow(mContainerView.getWindowToken(), 0);
+    }
+}
diff --git a/src/com/android/launcher3/widget/WidgetsContainerView.java b/src/com/android/launcher3/widget/WidgetsContainerView.java
index 51f2a5f..500311a 100644
--- a/src/com/android/launcher3/widget/WidgetsContainerView.java
+++ b/src/com/android/launcher3/widget/WidgetsContainerView.java
@@ -66,6 +66,7 @@
     private IconCache mIconCache;
 
     /* Recycler view related member variables */
+    private View mContent;
     private WidgetsRecyclerView mView;
     private WidgetsListAdapter mAdapter;
 
@@ -98,6 +99,7 @@
 
     @Override
     protected void onFinishInflate() {
+        mContent = findViewById(R.id.content);
         mView = (WidgetsRecyclerView) findViewById(R.id.widgets_list_view);
         mView.setAdapter(mAdapter);
 
@@ -112,7 +114,6 @@
         });
         mPadding.set(getPaddingLeft(), getPaddingTop(), getPaddingRight(),
                 getPaddingBottom());
-        onUpdatePaddings();
     }
 
     //
@@ -335,33 +336,18 @@
     //
 
     @Override
-    protected void onUpdatePaddings() {
-        if (mFixedBounds.isEmpty()) {
-            // If there are no fixed bounds, then use the default padding and insets
-            setPadding(mPadding.left + mInsets.left, mPadding.top + mInsets.top,
-                    mPadding.right + mInsets.right, mPadding.bottom + mInsets.bottom);
-        } else {
-            // If there are fixed bounds, then we update the padding to reflect the fixed bounds.
-            setPadding(mFixedBounds.left, mFixedBounds.top, getMeasuredWidth() - mFixedBounds.right,
-                    mFixedBounds.bottom);
-        }
+    protected void onUpdateBackgroundAndPaddings(Rect searchBarBounds, Rect padding) {
+        // Apply the top-bottom padding to the content itself so that the launcher transition is
+        // clipped correctly
+        mContent.setPadding(0, padding.top, 0, padding.bottom);
 
-        int inset = mFixedBounds.isEmpty() ? mView.getScrollbarWidth() : mFixedBoundsContainerInset;
-        mView.setPadding(inset + mView.getScrollbarWidth(), inset,
-                inset, inset);
-    }
-
-    @Override
-    protected void onUpdateBackgrounds() {
-        InsetDrawable background;
-        // Update the background of the reveal view and list to be inset with the fixed bound
-        // insets instead of the default insets
-        // TODO: Use quantum_panel instead of quantum_panel_shape.
-        int inset = mFixedBounds.isEmpty() ? mView.getScrollbarWidth() : mFixedBoundsContainerInset;
-        background = new InsetDrawable(
-                getContext().getResources().getDrawable(R.drawable.quantum_panel_shape),
-                inset, 0, inset, 0);
-        mView.updateBackgroundPadding(background);
+        // TODO: Use quantum_panel_dark instead of quantum_panel_shape_dark.
+        InsetDrawable background = new InsetDrawable(
+                getResources().getDrawable(R.drawable.quantum_panel_shape_dark), padding.left, 0,
+                padding.right, 0);
+        mView.setBackground(background);
+        getRevealView().setBackground(background.getConstantState().newDrawable());
+        mView.updateBackgroundPadding(padding);
     }
 
     /**
diff --git a/src/com/android/launcher3/widget/WidgetsRecyclerView.java b/src/com/android/launcher3/widget/WidgetsRecyclerView.java
index 9d265f8..fa7e2f0 100644
--- a/src/com/android/launcher3/widget/WidgetsRecyclerView.java
+++ b/src/com/android/launcher3/widget/WidgetsRecyclerView.java
@@ -18,11 +18,9 @@
 
 import android.content.Context;
 import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
 import android.support.v7.widget.LinearLayoutManager;
 import android.util.AttributeSet;
 import android.view.View;
-
 import com.android.launcher3.BaseRecyclerView;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.model.WidgetsModel;
@@ -35,7 +33,6 @@
 
     private static final String TAG = "WidgetsRecyclerView";
     private WidgetsModel mWidgets;
-    private Rect mBackgroundPadding = new Rect();
 
     public WidgetsRecyclerView(Context context) {
         this(context, null);
@@ -61,10 +58,6 @@
         addOnItemTouchListener(this);
     }
 
-    public void updateBackgroundPadding(Drawable background) {
-        background.getPadding(mBackgroundPadding);
-    }
-
     /**
      * Sets the widget model in this view, used to determine the fast scroll position.
      */