Merge "Tab indicator of work tab should be in orange color" into ub-launcher3-master
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index b59e4ee..60cd0c2 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -17,7 +17,6 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.view.View;
 
@@ -25,8 +24,8 @@
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.LauncherStateManager.AnimationConfig;
 import com.android.launcher3.LauncherStateManager.StateHandler;
-import com.android.launcher3.anim.AnimationLayerSet;
 import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.anim.Interpolators;
 import com.android.quickstep.AnimatedFloat;
 import com.android.quickstep.RecentsView;
@@ -54,18 +53,18 @@
     }
 
     @Override
-    public void setStateWithAnimation(LauncherState toState, AnimationLayerSet layerViews,
-            AnimatorSet anim, AnimationConfig config) {
+    public void setStateWithAnimation(LauncherState toState,
+            AnimatorSetBuilder builder, AnimationConfig config) {
         ObjectAnimator progressAnim =
                 mTransitionProgress.animateToValue(toState == LauncherState.OVERVIEW ? 1 : 0);
         progressAnim.setDuration(config.duration);
         progressAnim.setInterpolator(Interpolators.LINEAR);
-        anim.play(progressAnim);
+        builder.play(progressAnim);
 
         ObjectAnimator visibilityAnim = animateVisibility(toState == LauncherState.OVERVIEW);
         visibilityAnim.setDuration(config.duration);
         visibilityAnim.setInterpolator(Interpolators.LINEAR);
-        anim.play(visibilityAnim);
+        builder.play(visibilityAnim);
     }
 
     public void setVisibility(boolean isVisible) {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/TwoStepSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/TwoStepSwipeController.java
new file mode 100644
index 0000000..9081865
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/TwoStepSwipeController.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.uioverrides;
+
+import static com.android.launcher3.LauncherState.ALL_APPS;
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
+import static com.android.launcher3.anim.SpringAnimationHandler.Y_DIRECTION;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.support.animation.SpringAnimation;
+import android.util.Log;
+import android.view.MotionEvent;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.allapps.AllAppsContainerView;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.SpringAnimationHandler;
+import com.android.launcher3.touch.SwipeDetector;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.launcher3.util.TouchController;
+
+import java.util.ArrayList;
+
+/**
+ * Handles vertical touch gesture on the DragLayer
+ */
+public class TwoStepSwipeController extends AnimatorListenerAdapter
+        implements TouchController, SwipeDetector.Listener {
+
+    private static final String TAG = "TwoStepSwipeController";
+
+    private static final float RECATCH_REJECTION_FRACTION = .0875f;
+    private static final int SINGLE_FRAME_MS = 16;
+
+    // Progress after which the transition is assumed to be a success in case user does not fling
+    private static final float SUCCESS_TRANSITION_PROGRESS = 0.5f;
+
+    private final Launcher mLauncher;
+    private final SwipeDetector mDetector;
+
+    private boolean mNoIntercept;
+    private int mStartContainerType;
+
+    private AnimatorPlaybackController mCurrentAnimation;
+    private LauncherState mToState;
+
+    private float mStartProgress;
+    // Ratio of transition process [0, 1] to drag displacement (px)
+    private float mProgressMultiplier;
+
+    private SpringAnimationHandler[] mSpringHandlers;
+
+    public TwoStepSwipeController(Launcher l) {
+        mLauncher = l;
+        mDetector = new SwipeDetector(l, this, SwipeDetector.VERTICAL);
+    }
+
+    private boolean canInterceptTouch(MotionEvent ev) {
+        if (!mLauncher.isInState(NORMAL) && !mLauncher.isInState(ALL_APPS)) {
+            // Don't listen for the swipe gesture if we are already in some other state.
+            return false;
+        }
+        if (mCurrentAnimation != null) {
+            // If we are already animating from a previous state, we can intercept.
+            return true;
+        }
+        if (mLauncher.isInState(ALL_APPS) && !mLauncher.getAppsView().shouldContainerScroll(ev)) {
+            return false;
+        }
+        if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public void onAnimationCancel(Animator animation) {
+        if (mCurrentAnimation != null && animation == mCurrentAnimation.getTarget()) {
+            Log.e(TAG, "Who dare cancel the animation when I am in control", new Exception());
+            mDetector.finishedScrolling();
+            mCurrentAnimation = null;
+        }
+    }
+
+    private void initSprings() {
+        AllAppsContainerView appsView = mLauncher.getAppsView();
+
+        SpringAnimationHandler handler = appsView.getSpringAnimationHandler();
+        if (handler == null) {
+            mSpringHandlers = new SpringAnimationHandler[0];
+            return;
+        }
+
+        ArrayList<SpringAnimationHandler> handlers = new ArrayList<>();
+        handlers.add(handler);
+
+        SpringAnimation searchSpring = appsView.getSearchUiManager().getSpringForFling();
+        if (searchSpring != null) {
+            SpringAnimationHandler searchHandler =
+                    new SpringAnimationHandler(Y_DIRECTION, handler.getFactory());
+            searchHandler.add(searchSpring, true /* setDefaultValues */);
+            handlers.add(searchHandler);
+        }
+
+        mSpringHandlers = handlers.toArray(new SpringAnimationHandler[handlers.size()]);
+    }
+
+    @Override
+    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+            mNoIntercept = !canInterceptTouch(ev);
+            if (mNoIntercept) {
+                return false;
+            }
+
+            // Now figure out which direction scroll events the controller will start
+            // calling the callbacks.
+            final int directionsToDetectScroll;
+            boolean ignoreSlopWhenSettling = false;
+
+            if (mCurrentAnimation != null) {
+                if (mCurrentAnimation.getProgressFraction() > 1 - RECATCH_REJECTION_FRACTION) {
+                    directionsToDetectScroll = SwipeDetector.DIRECTION_POSITIVE;
+                } else if (mCurrentAnimation.getProgressFraction() < RECATCH_REJECTION_FRACTION ) {
+                    directionsToDetectScroll = SwipeDetector.DIRECTION_NEGATIVE;
+                } else {
+                    directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH;
+                    ignoreSlopWhenSettling = true;
+                }
+            } else {
+                if (mLauncher.isInState(ALL_APPS)) {
+                    directionsToDetectScroll = SwipeDetector.DIRECTION_NEGATIVE;
+                    mStartContainerType = ContainerType.ALLAPPS;
+                } else {
+                    directionsToDetectScroll = SwipeDetector.DIRECTION_POSITIVE;
+                    mStartContainerType = mLauncher.getDragLayer().isEventOverHotseat(ev) ?
+                            ContainerType.HOTSEAT : ContainerType.WORKSPACE;
+                }
+            }
+
+            mDetector.setDetectableScrollConditions(
+                    directionsToDetectScroll, ignoreSlopWhenSettling);
+
+            if (mSpringHandlers == null) {
+                initSprings();
+            }
+        }
+
+        if (mNoIntercept) {
+            return false;
+        }
+
+        onControllerTouchEvent(ev);
+        return mDetector.isDraggingOrSettling();
+    }
+
+    @Override
+    public boolean onControllerTouchEvent(MotionEvent ev) {
+        for (SpringAnimationHandler h : mSpringHandlers) {
+            h.addMovement(ev);
+        }
+        return mDetector.onTouchEvent(ev);
+    }
+
+    @Override
+    public void onDragStart(boolean start) {
+        if (mCurrentAnimation == null) {
+            float range = getShiftRange();
+            long maxAccuracy = (long) (2 * range);
+
+            // Build current animation
+            mToState = mLauncher.isInState(ALL_APPS) ? NORMAL : ALL_APPS;
+            mCurrentAnimation = mLauncher.getStateManager()
+                    .createAnimationToNewWorkspace(mToState, maxAccuracy);
+            mCurrentAnimation.getTarget().addListener(this);
+            mStartProgress = 0;
+            mProgressMultiplier = (mLauncher.isInState(ALL_APPS) ? 1 : -1) / range;
+            mCurrentAnimation.dispatchOnStart();
+        } else {
+            mCurrentAnimation.pause();
+            mStartProgress = mCurrentAnimation.getProgressFraction();
+        }
+
+        for (SpringAnimationHandler h : mSpringHandlers) {
+            h.skipToEnd();
+        }
+    }
+
+    private float getShiftRange() {
+        return mLauncher.getAllAppsController().getShiftRange();
+    }
+
+    @Override
+    public boolean onDrag(float displacement, float velocity) {
+        float deltaProgress = mProgressMultiplier * displacement;
+        mCurrentAnimation.setPlayFraction(deltaProgress + mStartProgress);
+        return true;
+    }
+
+    @Override
+    public void onDragEnd(float velocity, boolean fling) {
+        final long animationDuration;
+        final int logAction;
+        final LauncherState targetState;
+        final float progress = mCurrentAnimation.getProgressFraction();
+
+        if (fling) {
+            logAction = Touch.FLING;
+            if (velocity < 0) {
+                targetState = ALL_APPS;
+                animationDuration = SwipeDetector.calculateDuration(velocity,
+                        mToState == ALL_APPS ? (1 - progress) : progress);
+            } else {
+                targetState = NORMAL;
+                animationDuration = SwipeDetector.calculateDuration(velocity,
+                        mToState == ALL_APPS ? progress : (1 - progress));
+            }
+            // snap to top or bottom using the release velocity
+        } else {
+            logAction = Touch.SWIPE;
+            if (progress > SUCCESS_TRANSITION_PROGRESS) {
+                targetState = mToState;
+                animationDuration = SwipeDetector.calculateDuration(velocity, 1 - progress);
+            } else {
+                targetState = mToState == ALL_APPS ? NORMAL : ALL_APPS;
+                animationDuration = SwipeDetector.calculateDuration(velocity, progress);
+            }
+        }
+
+        if (fling && targetState == ALL_APPS) {
+            for (SpringAnimationHandler h : mSpringHandlers) {
+                // The icons are moving upwards, so we go to 0 from 1. (y-axis 1 is below 0.)
+                h.animateToFinalPosition(0 /* pos */, 1 /* startValue */);
+            }
+        }
+
+        mCurrentAnimation.setEndAction(new Runnable() {
+            @Override
+            public void run() {
+                if (targetState == mToState) {
+                    // Transition complete. log the action
+                    mLauncher.getUserEventDispatcher().logActionOnContainer(logAction,
+                            mToState == ALL_APPS ? Direction.UP : Direction.DOWN,
+                            mStartContainerType, mLauncher.getWorkspace().getCurrentPage());
+                } else {
+                    mLauncher.getStateManager().goToState(
+                            mToState == ALL_APPS ? NORMAL : ALL_APPS, false);
+                }
+                mDetector.finishedScrolling();
+                mCurrentAnimation = null;
+            }
+        });
+
+        float nextFrameProgress = Utilities.boundToRange(
+                progress + velocity * SINGLE_FRAME_MS / getShiftRange(), 0f, 1f);
+
+        ValueAnimator anim = mCurrentAnimation.getAnimationPlayer();
+        anim.setFloatValues(nextFrameProgress, targetState == mToState ? 1f : 0f);
+        anim.setDuration(animationDuration);
+        anim.setInterpolator(scrollInterpolatorForVelocity(velocity));
+        anim.start();
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
index a48a65c..5e280b6 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
@@ -24,14 +24,13 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherStateManager.StateHandler;
 import com.android.launcher3.R;
-import com.android.launcher3.VerticalSwipeController;
 import com.android.launcher3.util.TouchController;
 import com.android.launcher3.widget.WidgetsFullSheet;
 
 public class UiFactory {
 
     public static TouchController[] createTouchControllers(Launcher launcher) {
-        return new TouchController[] {new VerticalSwipeController(launcher)};
+        return new TouchController[] {new TwoStepSwipeController(launcher)};
     }
 
     public static AccessibilityDelegate newPageIndicatorAccessibilityDelegate() {
diff --git a/res/layout/all_apps.xml b/res/layout/all_apps.xml
index 73ed38a..6750c72 100644
--- a/res/layout/all_apps.xml
+++ b/res/layout/all_apps.xml
@@ -48,12 +48,16 @@
             android:id="@+id/divider"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
+            android:layout_marginLeft="@dimen/all_apps_tabs_side_padding"
+            android:layout_marginRight="@dimen/all_apps_tabs_side_padding"
             android:layout_alignBottom="@+id/tabs" />
 
         <com.android.launcher3.allapps.PersonalWorkSlidingTabStrip
             android:id="@+id/tabs"
             android:layout_width="match_parent"
             android:layout_height="@dimen/all_apps_header_tab_height"
+            android:layout_marginLeft="@dimen/all_apps_tabs_side_padding"
+            android:layout_marginRight="@dimen/all_apps_tabs_side_padding"
             android:layout_below="@id/header_content"
             android:orientation="horizontal">
             <Button
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 294425d..3e34e16 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -132,4 +132,8 @@
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Pessoal"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Trabalho"</string>
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Perfil de trabalho"</string>
+    <!-- no translation found for bottom_work_tab_user_education_title (5785851780786322825) -->
+    <skip />
+    <!-- no translation found for bottom_work_tab_user_education_body (5834430249581360068) -->
+    <skip />
 </resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index e7f45c2..e22c7c2 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -96,6 +96,7 @@
     <dimen name="all_apps_prediction_row_divider_height">17dp</dimen>
     <dimen name="all_apps_work_profile_tab_footer_top_padding">16dp</dimen>
     <dimen name="all_apps_work_profile_tab_footer_bottom_padding">20dp</dimen>
+    <dimen name="all_apps_tabs_side_padding">12dp</dimen>
 
 <!-- Search bar in All Apps -->
     <dimen name="all_apps_header_max_elevation">3dp</dimen>
diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java
index bce0e2e..76c7845 100644
--- a/src/com/android/launcher3/BaseRecyclerView.java
+++ b/src/com/android/launcher3/BaseRecyclerView.java
@@ -17,13 +17,11 @@
 package com.android.launcher3;
 
 import android.content.Context;
-import android.graphics.Canvas;
 import android.support.v7.widget.RecyclerView;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
-import android.widget.TextView;
 
 import com.android.launcher3.views.RecyclerViewFastScroller;
 
@@ -91,8 +89,10 @@
      */
     private boolean handleTouchEvent(MotionEvent ev) {
         // Move to mScrollbar's coordinate system.
-        int left = getLeft() - mScrollbar.getLeft();
-        int top = getTop() - mScrollbar.getTop();
+        // We need to take parent into account (view pager's location)
+        ViewGroup parent = (ViewGroup) getParent();
+        int left = parent.getLeft() + getLeft() - mScrollbar.getLeft();
+        int top = parent.getTop() + getTop() - mScrollbar.getTop() - getScrollBarTop();
         ev.offsetLocation(left, top);
         try {
             return mScrollbar.handleTouchEvent(ev);
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index a9b1619..0c573ac 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -42,13 +42,11 @@
 import android.annotation.TargetApi;
 import android.app.ActivityOptions;
 import android.app.AlertDialog;
-import android.app.SearchManager;
 import android.appwidget.AppWidgetHostView;
 import android.appwidget.AppWidgetManager;
 import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
 import android.content.ComponentCallbacks2;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.content.DialogInterface;
@@ -70,8 +68,6 @@
 import android.os.StrictMode;
 import android.os.UserHandle;
 import android.support.annotation.Nullable;
-import android.text.Selection;
-import android.text.SpannableStringBuilder;
 import android.text.TextUtils;
 import android.text.method.TextKeyListener;
 import android.util.Log;
@@ -89,7 +85,6 @@
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.OvershootInterpolator;
-import android.view.inputmethod.InputMethodManager;
 import android.widget.Toast;
 
 import com.android.launcher3.DropTarget.DragObject;
@@ -243,8 +238,6 @@
     // that results in widgets being inflated in the wrong orientation.
     private int mOrientation;
 
-    private SpannableStringBuilder mDefaultKeySsb = null;
-
     @Thunk boolean mWorkspaceLoading = true;
 
     private boolean mPaused = true;
@@ -259,14 +252,13 @@
     private ModelWriter mModelWriter;
     private IconCache mIconCache;
     private LauncherAccessibilityDelegate mAccessibilityDelegate;
-    private boolean mHasFocus = false;
 
     private ObjectAnimator mScrimAnimator;
     private boolean mShouldFadeInScrim;
 
     private PopupDataProvider mPopupDataProvider;
 
-    private final ArrayList<Integer> mSynchronouslyBoundPages = new ArrayList<>();
+    private int mSynchronouslyBoundPage = PagedView.INVALID_PAGE;
 
     // We only want to get the SharedPreferences once since it does an FS stat each time we get
     // it from the context.
@@ -400,8 +392,7 @@
         }
 
         // For handling default keys
-        mDefaultKeySsb = new SpannableStringBuilder();
-        Selection.setSelection(mDefaultKeySsb, 0);
+        setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
 
         // On large interfaces, or on devices that a user has specifically enabled screen rotation,
         // we want the screen to auto-rotate based on the current orientation
@@ -851,8 +842,6 @@
             mLauncherCallbacks.onResume();
         }
 
-        clearTypedText();
-
         TraceHelper.endSection("ON_RESUME");
     }
 
@@ -920,72 +909,6 @@
         }
     }
 
-    @Override
-    public Object onRetainNonConfigurationInstance() {
-        // Flag the loader to stop early before switching
-        if (mModel.isCurrentCallbacks(this)) {
-            mModel.stopLoader();
-        }
-        //TODO(hyunyoungs): stop the widgets loader when there is a rotation.
-
-        return Boolean.TRUE;
-    }
-
-    // We can't hide the IME if it was forced open.  So don't bother
-    @Override
-    public void onWindowFocusChanged(boolean hasFocus) {
-        super.onWindowFocusChanged(hasFocus);
-        mHasFocus = hasFocus;
-
-        if (mLauncherCallbacks != null) {
-            mLauncherCallbacks.onWindowFocusChanged(hasFocus);
-        }
-    }
-
-    private boolean acceptFilter() {
-        final InputMethodManager inputManager = (InputMethodManager)
-                getSystemService(Context.INPUT_METHOD_SERVICE);
-        return !inputManager.isFullscreenMode();
-    }
-
-    @Override
-    public boolean onKeyDown(int keyCode, KeyEvent event) {
-        final int uniChar = event.getUnicodeChar();
-        final boolean handled = super.onKeyDown(keyCode, event);
-        final boolean isKeyNotWhitespace = uniChar > 0 && !Character.isWhitespace(uniChar);
-        if (!handled && acceptFilter() && isKeyNotWhitespace) {
-            boolean gotKey = TextKeyListener.getInstance().onKeyDown(mWorkspace, mDefaultKeySsb,
-                    keyCode, event);
-            if (gotKey && mDefaultKeySsb != null && mDefaultKeySsb.length() > 0) {
-                // something usable has been typed - start a search
-                // the typed text will be retrieved and cleared by
-                // showSearchDialog()
-                // If there are multiple keystrokes before the search dialog takes focus,
-                // onSearchRequested() will be called for every keystroke,
-                // but it is idempotent, so it's fine.
-                return onSearchRequested();
-            }
-        }
-
-        // Eat the long press event so the keyboard doesn't come up.
-        if (keyCode == KeyEvent.KEYCODE_MENU && event.isLongPress()) {
-            return true;
-        }
-
-        return handled;
-    }
-
-    private String getTypedText() {
-        return mDefaultKeySsb.toString();
-    }
-
-    @Override
-    public void clearTypedText() {
-        mDefaultKeySsb.clear();
-        mDefaultKeySsb.clearSpans();
-        Selection.setSelection(mDefaultKeySsb, 0);
-    }
-
     public boolean isInState(LauncherState state) {
         return mStateManager.getState() == state;
     }
@@ -1337,7 +1260,7 @@
         TraceHelper.beginSection("NEW_INTENT");
         super.onNewIntent(intent);
 
-        boolean alreadyOnHome = mHasFocus && ((intent.getFlags() &
+        boolean alreadyOnHome = hasWindowFocus() && ((intent.getFlags() &
                 Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT)
                 != Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
 
@@ -1393,9 +1316,7 @@
     @Override
     public void onRestoreInstanceState(Bundle state) {
         super.onRestoreInstanceState(state);
-        for (int page: mSynchronouslyBoundPages) {
-            mWorkspace.restoreInstanceStateForChild(page);
-        }
+        mWorkspace.restoreInstanceStateForChild(mSynchronouslyBoundPage);
     }
 
     @Override
@@ -1459,7 +1380,6 @@
         } catch (NullPointerException ex) {
             Log.w(TAG, "problem while stopping AppWidgetHost during Launcher destruction", ex);
         }
-        mAppWidgetHost = null;
 
         TextKeyListener.getInstance().release();
         WallpaperColorInfo.getInstance(this).setOnThemeChangeListener(null);
@@ -1504,11 +1424,6 @@
     @Override
     public void startSearch(String initialQuery, boolean selectInitialQuery,
             Bundle appSearchData, boolean globalSearch) {
-
-        if (initialQuery == null) {
-            // Use any text typed in the launcher as the initial query
-            initialQuery = getTypedText();
-        }
         if (appSearchData == null) {
             appSearchData = new Bundle();
             appSearchData.putString("source", "launcher-search");
@@ -1517,69 +1432,13 @@
         if (mLauncherCallbacks == null ||
                 !mLauncherCallbacks.startSearch(initialQuery, selectInitialQuery, appSearchData)) {
             // Starting search from the callbacks failed. Start the default global search.
-            startGlobalSearch(initialQuery, selectInitialQuery, appSearchData, null);
+            super.startSearch(initialQuery, selectInitialQuery, appSearchData, true);
         }
 
         // We need to show the workspace after starting the search
         mStateManager.goToState(NORMAL);
     }
 
-    /**
-     * Starts the global search activity. This code is a copied from SearchManager
-     */
-    public void startGlobalSearch(String initialQuery,
-            boolean selectInitialQuery, Bundle appSearchData, Rect sourceBounds) {
-        final SearchManager searchManager =
-            (SearchManager) getSystemService(Context.SEARCH_SERVICE);
-        ComponentName globalSearchActivity = searchManager.getGlobalSearchActivity();
-        if (globalSearchActivity == null) {
-            Log.w(TAG, "No global search activity found.");
-            return;
-        }
-        Intent intent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH);
-        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        intent.setComponent(globalSearchActivity);
-        // Make sure that we have a Bundle to put source in
-        if (appSearchData == null) {
-            appSearchData = new Bundle();
-        } else {
-            appSearchData = new Bundle(appSearchData);
-        }
-        // Set source to package name of app that starts global search if not set already.
-        if (!appSearchData.containsKey("source")) {
-            appSearchData.putString("source", getPackageName());
-        }
-        intent.putExtra(SearchManager.APP_DATA, appSearchData);
-        if (!TextUtils.isEmpty(initialQuery)) {
-            intent.putExtra(SearchManager.QUERY, initialQuery);
-        }
-        if (selectInitialQuery) {
-            intent.putExtra(SearchManager.EXTRA_SELECT_QUERY, selectInitialQuery);
-        }
-        intent.setSourceBounds(sourceBounds);
-        try {
-            startActivity(intent);
-        } catch (ActivityNotFoundException ex) {
-            Log.e(TAG, "Global search activity not found: " + globalSearchActivity);
-        }
-    }
-
-    @Override
-    public boolean onPrepareOptionsMenu(Menu menu) {
-        super.onPrepareOptionsMenu(menu);
-        if (mLauncherCallbacks != null) {
-            return mLauncherCallbacks.onPrepareOptionsMenu(menu);
-        }
-        return false;
-    }
-
-    @Override
-    public boolean onSearchRequested() {
-        startSearch(null, false, null, true);
-        // Use a custom animation for launching search
-        return true;
-    }
-
     public boolean isWorkspaceLocked() {
         return mWorkspaceLoading || mPendingRequestArgs != null;
     }
@@ -2767,7 +2626,7 @@
     }
 
     public void onPageBoundSynchronously(int page) {
-        mSynchronouslyBoundPages.add(page);
+        mSynchronouslyBoundPage = page;
     }
 
     @Override
@@ -2817,11 +2676,7 @@
      * Implementation of the method from LauncherModel.Callbacks.
      */
     public void finishBindingItems() {
-        Runnable r = new Runnable() {
-            public void run() {
-                finishBindingItems();
-            }
-        };
+        Runnable r = this::finishBindingItems;
         if (waitUntilResume(r)) {
             return;
         }
diff --git a/src/com/android/launcher3/LauncherCallbacks.java b/src/com/android/launcher3/LauncherCallbacks.java
index 928258f..914d9eb 100644
--- a/src/com/android/launcher3/LauncherCallbacks.java
+++ b/src/com/android/launcher3/LauncherCallbacks.java
@@ -48,10 +48,8 @@
     void onActivityResult(int requestCode, int resultCode, Intent data);
     void onRequestPermissionsResult(int requestCode, String[] permissions,
             int[] grantResults);
-    void onWindowFocusChanged(boolean hasFocus);
     void onAttachedToWindow();
     void onDetachedFromWindow();
-    boolean onPrepareOptionsMenu(Menu menu);
     void dump(String prefix, FileDescriptor fd, PrintWriter w, String[] args);
     void onHomeIntent();
     boolean handleBackPressed();
diff --git a/src/com/android/launcher3/LauncherExterns.java b/src/com/android/launcher3/LauncherExterns.java
index 887859c..272bbf6 100644
--- a/src/com/android/launcher3/LauncherExterns.java
+++ b/src/com/android/launcher3/LauncherExterns.java
@@ -24,11 +24,9 @@
  */
 public interface LauncherExterns {
 
-    public boolean setLauncherCallbacks(LauncherCallbacks callbacks);
+    boolean setLauncherCallbacks(LauncherCallbacks callbacks);
 
-    public SharedPreferences getSharedPrefs();
+    SharedPreferences getSharedPrefs();
 
-    public void setLauncherOverlay(Launcher.LauncherOverlay overlay);
-
-    void clearTypedText();
+    void setLauncherOverlay(Launcher.LauncherOverlay overlay);
 }
diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/LauncherStateManager.java
index 2cad95e..e3768e2 100644
--- a/src/com/android/launcher3/LauncherStateManager.java
+++ b/src/com/android/launcher3/LauncherStateManager.java
@@ -25,9 +25,9 @@
 import android.os.Looper;
 import android.view.View;
 
-import com.android.launcher3.anim.AnimationLayerSet;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.uioverrides.UiFactory;
 
 /**
@@ -178,7 +178,8 @@
         // transition plays in reverse and use the same duration as previous state.
         mConfig.duration = state == NORMAL ? mState.transitionDuration : state.transitionDuration;
 
-        AnimatorSet animation = createAnimationToNewWorkspaceInternal(state, onCompleteRunnable);
+        AnimatorSet animation = createAnimationToNewWorkspaceInternal(
+                state, new AnimatorSetBuilder(), onCompleteRunnable);
         Runnable runnable = new StartAnimRunnable(animation, state.getFinalFocus(mLauncher));
         if (delay > 0) {
             mUiHandler.postDelayed(runnable, delay);
@@ -196,22 +197,26 @@
      */
     public AnimatorPlaybackController createAnimationToNewWorkspace(
             LauncherState state, long duration) {
+        return createAnimationToNewWorkspace(state, new AnimatorSetBuilder(), duration);
+    }
+
+    public AnimatorPlaybackController createAnimationToNewWorkspace(
+            LauncherState state, AnimatorSetBuilder builder, long duration) {
         mConfig.reset();
         mConfig.userControlled = true;
         mConfig.duration = duration;
         return AnimatorPlaybackController.wrap(
-                createAnimationToNewWorkspaceInternal(state, null), duration);
+                createAnimationToNewWorkspaceInternal(state, builder, null), duration);
     }
 
     protected AnimatorSet createAnimationToNewWorkspaceInternal(final LauncherState state,
-            final Runnable onCompleteRunnable) {
-        final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
-        final AnimationLayerSet layerViews = new AnimationLayerSet();
-
+            AnimatorSetBuilder builder, final Runnable onCompleteRunnable) {
         for (StateHandler handler : getStateHandlers()) {
-            handler.setStateWithAnimation(state, layerViews, animation, mConfig);
+            builder.startTag(handler);
+            handler.setStateWithAnimation(state, builder, mConfig);
         }
-        animation.addListener(layerViews);
+
+        final AnimatorSet animation = builder.build();
         animation.addListener(new AnimationSuccessListener() {
 
             @Override
@@ -330,8 +335,8 @@
         /**
          * Sets the UI to {@param state} by animating any changes.
          */
-        void setStateWithAnimation(LauncherState toState, AnimationLayerSet layerViews,
-                AnimatorSet anim, AnimationConfig config);
+        void setStateWithAnimation(LauncherState toState,
+                AnimatorSetBuilder builder, AnimationConfig config);
     }
 
     public interface StateListener {
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 3d59bad..9dc8bb7 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -26,7 +26,6 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
 import android.animation.LayoutTransition;
 import android.animation.ObjectAnimator;
 import android.animation.PropertyValuesHolder;
@@ -63,7 +62,7 @@
 import com.android.launcher3.accessibility.AccessibleDragListenerAdapter;
 import com.android.launcher3.accessibility.OverviewScreenAccessibilityDelegate;
 import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
-import com.android.launcher3.anim.AnimationLayerSet;
+import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.badge.FolderBadgeInfo;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
@@ -1547,10 +1546,10 @@
      * Sets the current workspace {@link LauncherState}, then animates the UI
      */
     @Override
-    public void setStateWithAnimation(LauncherState toState, AnimationLayerSet layerViews,
-            AnimatorSet anim, AnimationConfig config) {
+    public void setStateWithAnimation(LauncherState toState,
+            AnimatorSetBuilder builder, AnimationConfig config) {
         StateTransitionListener listener = new StateTransitionListener(toState);
-        mStateTransitionAnimation.setStateWithAnimation(toState, anim, layerViews, config);
+        mStateTransitionAnimation.setStateWithAnimation(toState, builder, config);
 
         // Invalidate the pages now, so that we have the visible pages before the
         // animation is started
@@ -1562,8 +1561,8 @@
         ValueAnimator stepAnimator = ValueAnimator.ofFloat(0, 1);
         stepAnimator.addUpdateListener(listener);
         stepAnimator.setDuration(config.duration);
-        anim.play(stepAnimator);
-        anim.addListener(listener);
+        stepAnimator.addListener(listener);
+        builder.play(stepAnimator);
     }
 
     public void updateAccessibilityFlags() {
@@ -3502,17 +3501,17 @@
 
             mRefreshPending = false;
 
-            mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
-                @Override
-                public boolean evaluate(ItemInfo info, View view) {
-                    if (view instanceof PendingAppWidgetHostView && mInfos.contains(info)) {
-                        mLauncher.removeItem(view, info, false /* deleteFromDb */);
-                        mLauncher.bindAppWidget((LauncherAppWidgetInfo) info);
-                    }
-                    // process all the shortcuts
-                    return false;
+            ArrayList<PendingAppWidgetHostView> views = new ArrayList<>(mInfos.size());
+            mapOverItems(MAP_NO_RECURSE, (info, view) -> {
+                if (view instanceof PendingAppWidgetHostView && mInfos.contains(info)) {
+                    views.add((PendingAppWidgetHostView) view);
                 }
+                // process all children
+                return false;
             });
+            for (PendingAppWidgetHostView view : views) {
+                view.reinflate();
+            }
         }
 
         @Override
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index edf5ada..abc140d 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -21,7 +21,6 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
@@ -32,7 +31,7 @@
 
 import com.android.launcher3.LauncherState.PageAlphaProvider;
 import com.android.launcher3.LauncherStateManager.AnimationConfig;
-import com.android.launcher3.anim.AnimationLayerSet;
+import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.anim.Interpolators;
 
 /**
@@ -110,10 +109,10 @@
         setWorkspaceProperty(toState, NO_ANIM_PROPERTY_SETTER);
     }
 
-    public void setStateWithAnimation(LauncherState toState, AnimatorSet anim,
-            AnimationLayerSet layerViews, AnimationConfig config) {
+    public void setStateWithAnimation(LauncherState toState, AnimatorSetBuilder builder,
+            AnimationConfig config) {
         AnimatedPropertySetter propertySetter =
-                new AnimatedPropertySetter(config.duration, layerViews, anim);
+                new AnimatedPropertySetter(config.duration, builder);
         setWorkspaceProperty(toState, propertySetter);
     }
 
@@ -189,14 +188,11 @@
     public static class AnimatedPropertySetter extends PropertySetter {
 
         private final long mDuration;
-        private final AnimationLayerSet mLayerViews;
-        private final AnimatorSet mStateAnimator;
+        private final AnimatorSetBuilder mStateAnimator;
 
-        public AnimatedPropertySetter(
-                long duration, AnimationLayerSet layerView, AnimatorSet anim) {
+        public AnimatedPropertySetter(long duration, AnimatorSetBuilder builder) {
             mDuration = duration;
-            mLayerViews = layerView;
-            mStateAnimator = anim;
+            mStateAnimator = builder;
         }
 
         @Override
@@ -210,7 +206,6 @@
             }
 
             anim.setDuration(mDuration).setInterpolator(getFadeInterpolator(alpha));
-            mLayerViews.addView(view);
             mStateAnimator.play(anim);
         }
 
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 8724c2c..7b89c5c 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -260,9 +260,6 @@
         if (mLauncher.getDragLayer().isEventOverView(mSearchContainer, ev)) {
             return true;
         }
-        if (mUsingTabs && mLauncher.getDragLayer().isEventOverView(mHeader, ev)) {
-            return true;
-        }
         AllAppsRecyclerView rv = getActiveRecyclerView();
         return rv == null || rv.shouldContainerScroll(ev, mLauncher.getDragLayer());
     }
@@ -554,20 +551,9 @@
             return;
         }
         mHeader.setVisibility(View.VISIBLE);
+        mHeader.setup(mAH, mComponentToAppMap, mNumPredictedAppsPerRow);
 
-        boolean usePredictionRow = mHasPredictions && !mSearchModeWhileUsingTabs;
-        int contentHeight = usePredictionRow ? mLauncher.getDeviceProfile().allAppsCellHeightPx : 0;;
-        if (usePredictionRow && !mUsingTabs) {
-            contentHeight += getResources()
-                    .getDimensionPixelSize(R.dimen.all_apps_prediction_row_divider_height);
-        }
-        AllAppsRecyclerView mainRV = mAH[AdapterHolder.MAIN].recyclerView;
-        AllAppsRecyclerView workRV = mAH[AdapterHolder.WORK].recyclerView;
-        mHeader.setup(mainRV, workRV, contentHeight);
-        mHeader.getPredictionRow().setup(mAH[AdapterHolder.MAIN].adapter,
-                mComponentToAppMap, mNumPredictedAppsPerRow);
-
-        int padding = contentHeight;
+        int padding = mHeader.getPredictionRow().getExpectedHeight();
         if (mHasPredictions && !mUsingTabs) {
             padding += mHeader.getPaddingTop() + mHeader.getPaddingBottom();
         }
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 7ce032f..5de58b4 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -5,7 +5,6 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.util.Property;
 import android.view.View;
@@ -19,8 +18,8 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
-import com.android.launcher3.anim.AnimationLayerSet;
 import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.graphics.GradientView;
 import com.android.launcher3.util.SystemUiController;
@@ -122,7 +121,7 @@
      * @param progress value between 0 and 1, 0 shows all apps and 1 shows workspace
      *
      * @see #setState(LauncherState)
-     * @see #setStateWithAnimation(LauncherState, AnimationLayerSet, AnimatorSet, AnimationConfig)
+     * @see #setStateWithAnimation(LauncherState, AnimatorSetBuilder, AnimationConfig)
      */
     public void setProgress(float progress) {
         mProgress = progress;
@@ -167,8 +166,8 @@
      * dependent UI using various animation events
      */
     @Override
-    public void setStateWithAnimation(LauncherState toState, AnimationLayerSet layerViews,
-            AnimatorSet animationOut, AnimationConfig config) {
+    public void setStateWithAnimation(LauncherState toState,
+            AnimatorSetBuilder builder, AnimationConfig config) {
         if (Float.compare(mProgress, toState.verticalProgress) == 0) {
             // Fail fast
             onProgressAnimationEnd();
@@ -182,7 +181,7 @@
         anim.setInterpolator(interpolator);
         anim.addListener(getProgressAnimatorListener());
 
-        animationOut.play(anim);
+        builder.play(anim);
     }
 
     public AnimatorListenerAdapter getProgressAnimatorListener() {
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderView.java b/src/com/android/launcher3/allapps/FloatingHeaderView.java
index 39e6818..dc3afb5 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderView.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderView.java
@@ -19,43 +19,38 @@
 import android.animation.ValueAnimator;
 import android.content.Context;
 import android.content.res.Resources;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.v7.widget.RecyclerView;
 import android.util.AttributeSet;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.RelativeLayout;
 
+import com.android.launcher3.AppInfo;
 import com.android.launcher3.R;
+import com.android.launcher3.util.ComponentKey;
+
+import java.util.HashMap;
 
 public class FloatingHeaderView extends RelativeLayout implements
         ValueAnimator.AnimatorUpdateListener {
 
-    private static final boolean SHOW_PREDICTIONS_ONLY_ON_TOP = true;
 
     private final Rect mClip = new Rect(0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE);
     private final ValueAnimator mAnimator = ValueAnimator.ofInt(0, 0);
+    private final Point mTempOffset = new Point();
     private final RecyclerView.OnScrollListener mOnScrollListener = new RecyclerView.OnScrollListener() {
         @Override
         public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
-            if (SHOW_PREDICTIONS_ONLY_ON_TOP) {
-                return;
-            }
-            if (!mTopOnlyMode && newState == RecyclerView.SCROLL_STATE_IDLE
-                    && mTranslationY != -mMaxTranslation && mTranslationY != 0) {
-                float scroll = Math.abs(getCurrentScroll());
-                boolean expand =  scroll > mMaxTranslation
-                        ? Math.abs(mTranslationY) < mMaxTranslation / 2 : true;
-                setExpanded(expand);
-            }
         }
 
         @Override
         public void onScrolled(RecyclerView rv, int dx, int dy) {
-            boolean isMainRV = rv == mMainRV;
-            if (isMainRV != mMainRVActive) {
+            if (rv != mCurrentRV) {
                 return;
             }
 
@@ -63,9 +58,7 @@
                 mAnimator.cancel();
             }
 
-            int current = - (isMainRV
-                    ? mMainRV.getCurrentScrollY()
-                    : mWorkRV.getCurrentScrollY());
+            int current = -mCurrentRV.getCurrentScrollY();
             moved(current);
             apply();
         }
@@ -76,14 +69,14 @@
     private View mDivider;
     private AllAppsRecyclerView mMainRV;
     private AllAppsRecyclerView mWorkRV;
-    private boolean mTopOnlyMode;
-    private boolean mHeaderHidden;
+    private AllAppsRecyclerView mCurrentRV;
+    private ViewGroup mParent;
+    private boolean mTabsHidden;
+    private boolean mHeaderCollapsed;
     private int mMaxTranslation;
     private int mSnappedScrolledY;
     private int mTranslationY;
-    private int mMainScrolledY;
-    private int mWorkScrolledY;
-    private boolean mMainRVActive;
+    private boolean mForwardToRecyclerView;
 
     public FloatingHeaderView(@NonNull Context context) {
         this(context, null);
@@ -101,14 +94,18 @@
         mPredictionRow = findViewById(R.id.header_content);
     }
 
-    public void setup(@NonNull AllAppsRecyclerView personalRV, @Nullable AllAppsRecyclerView workRV,
-                      int predictionRowHeight) {
-        mTopOnlyMode = workRV == null;
-        mTabLayout.setVisibility(mTopOnlyMode ? View.GONE : View.VISIBLE);
-        mPredictionRow.getLayoutParams().height = predictionRowHeight;
-        mMaxTranslation = predictionRowHeight;
-        mMainRV = setupRV(mMainRV, personalRV);
-        mWorkRV = setupRV(mWorkRV, workRV);
+    public void setup(AllAppsContainerView.AdapterHolder[] mAH,
+            HashMap<ComponentKey, AppInfo> componentToAppMap, int numPredictedAppsPerRow) {
+        mTabsHidden = mAH[AllAppsContainerView.AdapterHolder.WORK].recyclerView == null;
+        mTabLayout.setVisibility(mTabsHidden ? View.GONE : View.VISIBLE);
+        mPredictionRow.setPadding(0, 0, 0, mTabsHidden ? getResources()
+                .getDimensionPixelSize(R.dimen.all_apps_prediction_row_divider_height) : 0);
+        mPredictionRow.setup(mAH[AllAppsContainerView.AdapterHolder.MAIN].adapter,
+                componentToAppMap, numPredictedAppsPerRow);
+        mMaxTranslation = mPredictionRow.getExpectedHeight();
+        mMainRV = setupRV(mMainRV, mAH[AllAppsContainerView.AdapterHolder.MAIN].recyclerView);
+        mWorkRV = setupRV(mWorkRV, mAH[AllAppsContainerView.AdapterHolder.WORK].recyclerView);
+        mParent = (ViewGroup) mMainRV.getParent();
         setMainActive(true);
         setupDivider();
     }
@@ -124,16 +121,16 @@
         Resources res = getResources();
         int verticalGap = res.getDimensionPixelSize(R.dimen.all_apps_divider_margin_vertical);
         int sideGap = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
-        mDivider.setPadding(sideGap, verticalGap,sideGap, mTopOnlyMode ? verticalGap : 0);
+        mDivider.setPadding(sideGap, verticalGap,sideGap, mTabsHidden ? verticalGap : 0);
         RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) mDivider.getLayoutParams();
         lp.removeRule(RelativeLayout.ALIGN_BOTTOM);
-        lp.addRule(RelativeLayout.ALIGN_BOTTOM, mTopOnlyMode ? R.id.header_content : R.id.tabs);
+        lp.addRule(RelativeLayout.ALIGN_BOTTOM, mTabsHidden ? R.id.header_content : R.id.tabs);
         mDivider.setLayoutParams(lp);
     }
 
     public void setMainActive(boolean active) {
-        mMainRVActive = active;
-        mSnappedScrolledY = getCurrentScroll() - mMaxTranslation;
+        mCurrentRV = active ? mMainRV : mWorkRV;
+        mSnappedScrolledY = mCurrentRV.getCurrentScrollY() - mMaxTranslation;
         setExpanded(true);
     }
 
@@ -141,36 +138,29 @@
         return mPredictionRow;
     }
 
-    public ViewGroup getTabLayout() {
-        return mTabLayout;
-    }
-
     public View getDivider() {
         return mDivider;
     }
 
     public void reset() {
-        mMainScrolledY = 0;
-        mWorkScrolledY = 0;
         setExpanded(true);
     }
 
     private boolean canSnapAt(int currentScrollY) {
-        boolean snapOnlyOnTop = SHOW_PREDICTIONS_ONLY_ON_TOP || mTopOnlyMode;
-        return !snapOnlyOnTop || Math.abs(currentScrollY) <= mPredictionRow.getHeight();
+        return Math.abs(currentScrollY) <= mPredictionRow.getHeight();
     }
 
     private void moved(final int currentScrollY) {
-        if (mHeaderHidden) {
+        if (mHeaderCollapsed) {
             if (currentScrollY <= mSnappedScrolledY) {
                 if (canSnapAt(currentScrollY)) {
                     mSnappedScrolledY = currentScrollY;
                 }
             } else {
-                mHeaderHidden = false;
+                mHeaderCollapsed = false;
             }
             mTranslationY = currentScrollY;
-        } else if (!mHeaderHidden) {
+        } else if (!mHeaderCollapsed) {
             mTranslationY = currentScrollY - mSnappedScrolledY - mMaxTranslation;
 
             // update state vars
@@ -178,7 +168,7 @@
                 mTranslationY = 0;
                 mSnappedScrolledY = currentScrollY - mMaxTranslation;
             } else if (mTranslationY <= -mMaxTranslation) { // hide or stay hidden
-                mHeaderHidden = true;
+                mHeaderCollapsed = true;
                 mSnappedScrolledY = currentScrollY;
             }
         }
@@ -195,7 +185,7 @@
             mPredictionRow.setTranslationY(uncappedTranslationY);
         }
         mTabLayout.setTranslationY(mTranslationY);
-        mDivider.setTranslationY(mTopOnlyMode ? uncappedTranslationY : mTranslationY);
+        mDivider.setTranslationY(mTabsHidden ? uncappedTranslationY : mTranslationY);
         mClip.top = mMaxTranslation + mTranslationY;
         // clipping on a draw might cause additional redraw
         mMainRV.setClipBounds(mClip);
@@ -210,16 +200,14 @@
         mAnimator.addUpdateListener(this);
         mAnimator.setDuration(150);
         mAnimator.start();
-        mHeaderHidden = !expand;
-        mSnappedScrolledY = expand ? getCurrentScroll() - mMaxTranslation : getCurrentScroll();
+        mHeaderCollapsed = !expand;
+        mSnappedScrolledY = expand
+                ? mCurrentRV.getCurrentScrollY() - mMaxTranslation
+                : mCurrentRV.getCurrentScrollY();
     }
 
     public boolean isExpanded() {
-        return !mHeaderHidden;
-    }
-
-    private int getCurrentScroll() {
-        return mMainRVActive ? mMainScrolledY : mWorkScrolledY;
+        return !mHeaderCollapsed;
     }
 
     @Override
@@ -228,6 +216,36 @@
         apply();
     }
 
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        calcOffset(mTempOffset);
+        ev.offsetLocation(mTempOffset.x, mTempOffset.y);
+        mForwardToRecyclerView = mCurrentRV.onInterceptTouchEvent(ev);
+        ev.offsetLocation(-mTempOffset.x, -mTempOffset.y);
+        return mForwardToRecyclerView || super.onInterceptTouchEvent(ev);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        if (mForwardToRecyclerView) {
+            // take this view's and parent view's (view pager) location into account
+            calcOffset(mTempOffset);
+            event.offsetLocation(mTempOffset.x, mTempOffset.y);
+            try {
+                return mCurrentRV.onTouchEvent(event);
+            } finally {
+                event.offsetLocation(-mTempOffset.x, -mTempOffset.y);
+            }
+        } else {
+            return super.onTouchEvent(event);
+        }
+    }
+
+    private void calcOffset(Point p) {
+        p.x = getLeft() - mCurrentRV.getLeft() - mParent.getLeft();
+        p.y = getTop() - mCurrentRV.getTop() - mParent.getTop();
+    }
+
 }
 
 
diff --git a/src/com/android/launcher3/allapps/PredictionRowView.java b/src/com/android/launcher3/allapps/PredictionRowView.java
index ea91770..30c30c8 100644
--- a/src/com/android/launcher3/allapps/PredictionRowView.java
+++ b/src/com/android/launcher3/allapps/PredictionRowView.java
@@ -26,6 +26,7 @@
 
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.Launcher;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.ComponentKeyMapper;
@@ -57,11 +58,28 @@
         setOrientation(LinearLayout.HORIZONTAL);
     }
 
-    public void setup(AllAppsGridAdapter adapter,
-            HashMap<ComponentKey, AppInfo> componentToAppMap, int numPredictedAppsPerRow) {
+    public void setup(AllAppsGridAdapter adapter, HashMap<ComponentKey, AppInfo> componentToAppMap,
+                      int numPredictedAppsPerRow) {
         mAdapter = adapter;
         mComponentToAppMap = componentToAppMap;
         mNumPredictedAppsPerRow = numPredictedAppsPerRow;
+        setVisibility(mPredictedAppComponents.isEmpty() ? View.GONE : View.VISIBLE);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(getExpectedHeight(),
+                MeasureSpec.EXACTLY));
+    }
+
+    public int getExpectedHeight() {
+        int height = 0;
+        if (!mPredictedAppComponents.isEmpty()) {
+            height += Launcher.getLauncher(getContext())
+                    .getDeviceProfile().allAppsCellHeightPx;
+            height += getPaddingTop() + getPaddingBottom();
+        }
+        return height;
     }
 
     /**
diff --git a/src/com/android/launcher3/anim/AnimationLayerSet.java b/src/com/android/launcher3/anim/AnimationLayerSet.java
deleted file mode 100644
index f0b3458..0000000
--- a/src/com/android/launcher3/anim/AnimationLayerSet.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.anim;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.util.ArrayMap;
-import android.view.View;
-import java.util.Iterator;
-import java.util.Map;
-
-/**
- * Helper class to automatically build view hardware layers for the duration of an animation.
- */
-public class AnimationLayerSet extends AnimatorListenerAdapter {
-
-    private final ArrayMap<View, Integer> mViewsToLayerTypeMap;
-
-    public AnimationLayerSet() {
-        mViewsToLayerTypeMap = new ArrayMap<>();
-    }
-
-    public AnimationLayerSet(View v) {
-        mViewsToLayerTypeMap = new ArrayMap<>(1);
-        addView(v);
-    }
-
-    public void addView(View v) {
-        mViewsToLayerTypeMap.put(v, v.getLayerType());
-    }
-
-    @Override
-    public void onAnimationStart(Animator animation) {
-        // Enable all necessary layers
-        Iterator<Map.Entry<View, Integer>> itr = mViewsToLayerTypeMap.entrySet().iterator();
-        while (itr.hasNext()) {
-            Map.Entry<View, Integer> entry = itr.next();
-            View v = entry.getKey();
-            entry.setValue(v.getLayerType());
-            v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
-            if (v.isAttachedToWindow() && v.getVisibility() == View.VISIBLE) {
-                v.buildLayer();
-            }
-        }
-    }
-
-    @Override
-    public void onAnimationEnd(Animator animation) {
-        Iterator<Map.Entry<View, Integer>> itr = mViewsToLayerTypeMap.entrySet().iterator();
-        while (itr.hasNext()) {
-            Map.Entry<View, Integer> entry = itr.next();
-            entry.getKey().setLayerType(entry.getValue(), null);
-        }
-    }
-}
diff --git a/src/com/android/launcher3/anim/AnimatorSetBuilder.java b/src/com/android/launcher3/anim/AnimatorSetBuilder.java
new file mode 100644
index 0000000..63e67ff
--- /dev/null
+++ b/src/com/android/launcher3/anim/AnimatorSetBuilder.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.anim;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+
+import com.android.launcher3.LauncherAnimUtils;
+
+import java.util.ArrayList;
+
+/**
+ * Utility class for building animator set
+ */
+public class AnimatorSetBuilder {
+
+    protected final ArrayList<Animator> mAnims = new ArrayList<>();
+
+    /**
+     * Associates a tag with all the animations added after this call.
+     */
+    public void startTag(Object obj) { }
+
+    public void play(Animator anim) {
+        mAnims.add(anim);
+    }
+
+    public AnimatorSet build() {
+        AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
+        anim.playTogether(mAnims);
+        return anim;
+    }
+}
diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java
index 7cf3da0..0d92d45 100644
--- a/src/com/android/launcher3/config/BaseFlags.java
+++ b/src/com/android/launcher3/config/BaseFlags.java
@@ -60,7 +60,7 @@
     public static final boolean GO_DISABLE_WIDGETS = false;
 
     // When enabled shows a work profile tab in all apps
-    public static final boolean ALL_APPS_TABS_ENABLED = false;
+    public static final boolean ALL_APPS_TABS_ENABLED = true;
     // When enabled prediction row is rendered as it's own custom view
-    public static final boolean ALL_APPS_PREDICTION_ROW_VIEW = false;
+    public static final boolean ALL_APPS_PREDICTION_ROW_VIEW = true;
 }
diff --git a/src/com/android/launcher3/views/RecyclerViewFastScroller.java b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
index b36a0ff..fc121d3 100644
--- a/src/com/android/launcher3/views/RecyclerViewFastScroller.java
+++ b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
@@ -322,7 +322,7 @@
      * Returns whether the specified point is inside the thumb bounds.
      */
     private boolean isNearThumb(int x, int y) {
-        int offset = y - mRv.getScrollBarTop() - mThumbOffsetY;
+        int offset = y - mThumbOffsetY;
 
         return x >= 0 && x < getWidth() && offset >= 0 && offset <= mThumbHeight;
     }
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/OverviewPanel.java b/src_ui_overrides/com/android/launcher3/uioverrides/OverviewPanel.java
index 3ce1014..b23927b 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/OverviewPanel.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/OverviewPanel.java
@@ -17,7 +17,6 @@
 
 import static com.android.launcher3.WorkspaceStateTransitionAnimation.NO_ANIM_PROPERTY_SETTER;
 
-import android.animation.AnimatorSet;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
@@ -37,7 +36,7 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.WorkspaceStateTransitionAnimation.AnimatedPropertySetter;
 import com.android.launcher3.WorkspaceStateTransitionAnimation.PropertySetter;
-import com.android.launcher3.anim.AnimationLayerSet;
+import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
 import com.android.launcher3.widget.WidgetsFullSheet;
@@ -168,9 +167,9 @@
     }
 
     @Override
-    public void setStateWithAnimation(LauncherState toState, AnimationLayerSet layerViews,
-            AnimatorSet anim, AnimationConfig config) {
-        setState(toState, new AnimatedPropertySetter(config.duration, layerViews, anim));
+    public void setStateWithAnimation(LauncherState toState,
+            AnimatorSetBuilder builder, AnimationConfig config) {
+        setState(toState, new AnimatedPropertySetter(config.duration, builder));
     }
 
     private void setState(LauncherState state, PropertySetter setter) {