Merge "Add carousel curve effect to RecentsView." into ub-launcher3-master
diff --git a/quickstep/libs/sysui_shared.jar b/quickstep/libs/sysui_shared.jar
index ef50ac4..9069698 100644
--- a/quickstep/libs/sysui_shared.jar
+++ b/quickstep/libs/sysui_shared.jar
Binary files differ
diff --git a/quickstep/src/com/android/quickstep/MotionEventQueue.java b/quickstep/src/com/android/quickstep/MotionEventQueue.java
new file mode 100644
index 0000000..e3c3a1b
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/MotionEventQueue.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep;
+
+import static android.view.MotionEvent.ACTION_CANCEL;
+import static android.view.MotionEvent.ACTION_MOVE;
+
+import android.view.Choreographer;
+import android.view.MotionEvent;
+
+import com.android.systemui.shared.system.ChoreographerCompat;
+
+import java.util.ArrayList;
+import java.util.function.Consumer;
+
+/**
+ * Helper class for batching input events
+ */
+public class MotionEventQueue implements Runnable {
+
+    // We use two arrays and swap the current index when one array is being consumed
+    private final EventArray[] mArrays = new EventArray[] {new EventArray(), new EventArray()};
+    private int mCurrentIndex = 0;
+
+    private final Choreographer mChoreographer;
+    private final Consumer<MotionEvent> mConsumer;
+
+    public MotionEventQueue(Choreographer choreographer, Consumer<MotionEvent> consumer) {
+        mChoreographer = choreographer;
+        mConsumer = consumer;
+    }
+
+    public void queue(MotionEvent event) {
+        synchronized (mArrays) {
+            EventArray array = mArrays[mCurrentIndex];
+            if (array.isEmpty()) {
+                ChoreographerCompat.postInputFrame(mChoreographer, this);
+            }
+
+            int eventAction = event.getAction();
+            if (eventAction == ACTION_MOVE && array.lastEventAction == ACTION_MOVE) {
+                // Replace and recycle the last event
+                array.set(array.size() - 1, event).recycle();
+            } else {
+                array.add(event);
+                array.lastEventAction = eventAction;
+            }
+        }
+    }
+
+    @Override
+    public void run() {
+        EventArray array = swapAndGetCurrentArray();
+        int size = array.size();
+        for (int i = 0; i < size; i++) {
+            MotionEvent event = array.get(i);
+            mConsumer.accept(event);
+            event.recycle();
+        }
+        array.clear();
+        array.lastEventAction = ACTION_CANCEL;
+    }
+
+    private EventArray swapAndGetCurrentArray() {
+        synchronized (mArrays) {
+            EventArray current = mArrays[mCurrentIndex];
+            mCurrentIndex = mCurrentIndex ^ 1;
+            return current;
+        }
+    }
+
+    private static class EventArray extends ArrayList<MotionEvent> {
+
+        public int lastEventAction = ACTION_CANCEL;
+
+        public EventArray() {
+            super(4);
+        }
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java b/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java
index dc7d648..af82fe9 100644
--- a/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java
+++ b/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java
@@ -28,11 +28,7 @@
 import android.os.Build;
 import android.os.Handler;
 import android.os.UserHandle;
-import android.support.annotation.BinderThread;
 import android.support.annotation.UiThread;
-import android.util.DisplayMetrics;
-import android.view.Choreographer;
-import android.view.Choreographer.FrameCallback;
 import android.view.View;
 import android.view.ViewTreeObserver.OnPreDrawListener;
 
@@ -46,16 +42,15 @@
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.states.InternalStateHandler;
+import com.android.launcher3.util.TraceHelper;
 import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.Task.TaskKey;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.WindowManagerWrapper;
 
-import java.util.concurrent.atomic.AtomicBoolean;
-
 @TargetApi(Build.VERSION_CODES.O)
-public class NavBarSwipeInteractionHandler extends InternalStateHandler implements FrameCallback {
+public class NavBarSwipeInteractionHandler extends InternalStateHandler {
 
     private static final int STATE_LAUNCHER_READY = 1 << 0;
     private static final int STATE_RECENTS_DELAY_COMPLETE = 1 << 1;
@@ -90,9 +85,6 @@
     // animated to 1, so allow for a smooth transition.
     private final AnimatedFloat mActivityMultiplier = new AnimatedFloat(this::updateFinalShift);
 
-    private final Choreographer mChoreographer;
-    private final AtomicBoolean mFrameScheduled = new AtomicBoolean(false);
-
     private final int mRunningTaskId;
     private final Context mContext;
 
@@ -106,18 +98,12 @@
 
     private boolean mLauncherReady;
     private boolean mTouchEndHandled;
+    private float mCurrentDisplacement;
 
     private Bitmap mTaskSnapshot;
 
-    // These are updated on the binder thread, and eventually picked up on doFrame
-    private volatile float mCurrentDisplacement;
-    private volatile float mEndVelocity;
-    private volatile boolean mTouchEnded = false;
-
-    NavBarSwipeInteractionHandler(
-            RunningTaskInfo runningTaskInfo, Choreographer choreographer, Context context) {
+    NavBarSwipeInteractionHandler(RunningTaskInfo runningTaskInfo, Context context) {
         mRunningTaskId = runningTaskInfo.id;
-        mChoreographer = choreographer;
         mContext = context;
         WindowManagerWrapper.getInstance().getStableInsets(mStableInsets);
 
@@ -168,11 +154,13 @@
 
     @Override
     public void onLauncherResume() {
+        TraceHelper.partitionSection("TouchInt", "Launcher On resume");
         mDragView.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
             @Override
             public boolean onPreDraw() {
                 mDragView.getViewTreeObserver().removeOnPreDrawListener(this);
                 mStateCallback.setState(STATE_LAUNCHER_READY);
+                TraceHelper.partitionSection("TouchInt", "Launcher drawn");
                 return true;
             }
         });
@@ -194,39 +182,12 @@
         // Optimization
         mLauncher.getAppsView().setVisibility(View.GONE);
         mRecentsView.setVisibility(View.GONE);
+        TraceHelper.partitionSection("TouchInt", "Launcher on new intent");
     }
 
-    /**
-     * This is updated on the binder thread and is picked up on the UI thread during the next
-     * scheduled frame.
-     * TODO: Instead of continuously scheduling frames, post the motion events to UI thread
-     * (can ignore all continuous move events until the last move).
-     */
-    @BinderThread
+    @UiThread
     public void updateDisplacement(float displacement) {
         mCurrentDisplacement = displacement;
-        scheduleFrameIfNeeded();
-    }
-
-    @BinderThread
-    public void endTouch(float endVelocity) {
-        mEndVelocity = endVelocity;
-        mTouchEnded = true;
-        scheduleFrameIfNeeded();
-    }
-
-    private void scheduleFrameIfNeeded() {
-        boolean alreadyScheduled = mFrameScheduled.getAndSet(true);
-        if (!alreadyScheduled) {
-            // TODO: Here we might end up scheduling one additional frame in some race conditions.
-            // This can be avoided by synchronising postFrameCallback as well
-            mChoreographer.postFrameCallback(this);
-        }
-    }
-
-    @Override
-    public void doFrame(long l) {
-        mFrameScheduled.set(false);
         executeFrameUpdate();
     }
 
@@ -238,14 +199,6 @@
             float shift = hotseatHeight == 0 ? 0 : translation / hotseatHeight;
             mCurrentShift.updateValue(shift);
         }
-
-        if (mTouchEnded) {
-            if (mTouchEndHandled) {
-                return;
-            }
-            mTouchEndHandled = true;
-            animateToFinalShift();
-        }
     }
 
     @UiThread
@@ -301,25 +254,30 @@
     }
 
     @UiThread
-    private void animateToFinalShift() {
+    public void endTouch(float endVelocity) {
+        if (mTouchEndHandled) {
+            return;
+        }
+        mTouchEndHandled = true;
+
         Resources res = mContext.getResources();
         float flingThreshold = res.getDimension(R.dimen.quickstep_fling_threshold_velocity);
-        boolean isFling = Math.abs(mEndVelocity) > flingThreshold;
+        boolean isFling = Math.abs(endVelocity) > flingThreshold;
 
         long duration = DEFAULT_SWIPE_DURATION;
         final float endShift;
         if (!isFling) {
             endShift = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW ? 1 : 0;
         } else {
-            endShift = mEndVelocity < 0 ? 1 : 0;
+            endShift = endVelocity < 0 ? 1 : 0;
             float minFlingVelocity = res.getDimension(R.dimen.quickstep_fling_min_velocity);
-            if (Math.abs(mEndVelocity) > minFlingVelocity && mLauncherReady) {
+            if (Math.abs(endVelocity) > minFlingVelocity && mLauncherReady) {
                 float distanceToTravel = (endShift - mCurrentShift.value) * mHotseat.getHeight();
 
                 // we want the page's snap velocity to approximately match the velocity at
                 // which the user flings, so we scale the duration by a value near to the
                 // derivative of the scroll interpolator at zero, ie. 5.
-                duration = 5 * Math.round(1000 * Math.abs(distanceToTravel / mEndVelocity));
+                duration = 5 * Math.round(1000 * Math.abs(distanceToTravel / endVelocity));
             }
         }
 
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index a0340b6..f92d773 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -22,6 +22,7 @@
 import android.widget.ArrayAdapter;
 
 import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
+import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan.PreloadOptions;
 import com.android.systemui.shared.recents.model.RecentsTaskLoader;
 import com.android.systemui.shared.recents.model.Task;
 
@@ -37,7 +38,8 @@
         super.onCreate(savedInstanceState);
 
         RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(this);
-        plan.preloadPlan(new RecentsTaskLoader(this, 1, 1, 0), -1, UserHandle.myUserId());
+        plan.preloadPlan(new PreloadOptions(), new RecentsTaskLoader(this, 1, 1, 0), -1,
+                UserHandle.myUserId());
 
         mAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1);
         mAdapter.addAll(plan.getTaskStack().getTasks());
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 50f5528..55fd448 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -42,9 +42,11 @@
 
 import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.R;
+import com.android.launcher3.util.TraceHelper;
 import com.android.systemui.shared.recents.IOverviewProxy;
 import com.android.systemui.shared.recents.ISystemUiProxy;
 import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
+import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan.PreloadOptions;
 import com.android.systemui.shared.recents.model.RecentsTaskLoader;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.BackgroundExecutor;
@@ -62,7 +64,7 @@
 
         @Override
         public void onMotionEvent(MotionEvent ev) {
-            handleMotionEvent(ev);
+            mEventQueue.queue(ev);
         }
 
         @Override
@@ -75,7 +77,7 @@
     private RunningTaskInfo mRunningTask;
     private Intent mHomeIntent;
     private ComponentName mLauncher;
-    private Choreographer mChoreographer;
+    private MotionEventQueue mEventQueue;
     private MainThreadExecutor mMainThreadExecutor;
 
     private int mDisplayRotation;
@@ -110,8 +112,8 @@
             sRecentsTaskLoader.startLoader(this);
         }
 
-        mChoreographer = Choreographer.getInstance();
         mMainThreadExecutor = new MainThreadExecutor();
+        mEventQueue = new MotionEventQueue(Choreographer.getInstance(), this::handleMotionEvent);
     }
 
     @Override
@@ -130,6 +132,7 @@
         }
         switch (ev.getActionMasked()) {
             case MotionEvent.ACTION_DOWN: {
+                TraceHelper.beginSection("TouchInt");
                 mActivePointerId = ev.getPointerId(0);
                 mDownPos.set(ev.getX(), ev.getY());
                 mLastPos.set(mDownPos);
@@ -192,6 +195,7 @@
             case MotionEvent.ACTION_CANCEL:
                 // TODO: Should be different than ACTION_UP
             case MotionEvent.ACTION_UP: {
+                TraceHelper.endSection("TouchInt");
 
                 endInteraction();
                 break;
@@ -202,12 +206,13 @@
     private void startTouchTracking() {
         // Create the shared handler
         final NavBarSwipeInteractionHandler handler =
-                new NavBarSwipeInteractionHandler(mRunningTask, mChoreographer, this);
+                new NavBarSwipeInteractionHandler(mRunningTask, this);
 
         // Preload and start the recents activity on a background thread
         final Context context = this;
         final RecentsTaskLoadPlan loadPlan = new RecentsTaskLoadPlan(context);
         final int taskId = mRunningTask.id;
+        TraceHelper.partitionSection("TouchInt", "Thershold crossed ");
 
         BackgroundExecutor.get().submit(() -> {
             // Get the snap shot before
@@ -216,6 +221,8 @@
             // Start the launcher activity with our custom handler
             Intent homeIntent = handler.addToIntent(new Intent(mHomeIntent));
             startActivity(homeIntent, ActivityOptions.makeCustomAnimation(this, 0, 0).toBundle());
+            TraceHelper.partitionSection("TouchInt", "Home started");
+
             /*
             ActivityManagerWrapper.getInstance().startRecentsActivity(null, options,
                     ActivityOptions.makeCustomAnimation(this, 0, 0), UserHandle.myUserId(),
@@ -224,7 +231,9 @@
 
             // Preload the plan
             RecentsTaskLoader loader = TouchInteractionService.getRecentsTaskLoader();
-            loadPlan.preloadPlan(loader, taskId, UserHandle.myUserId());
+            PreloadOptions opts = new PreloadOptions();
+            opts.loadTitles = false;
+            loadPlan.preloadPlan(opts, loader, taskId, UserHandle.myUserId());
             // Set the load plan on UI thread
             mMainThreadExecutor.execute(() -> handler.setRecentsTaskLoadPlan(loadPlan));
         });
@@ -249,13 +258,16 @@
             return null;
         }
 
+        TraceHelper.beginSection("TaskSnapshot");
         // TODO: We are using some hardcoded layers for now, to best approximate the activity layers
         try {
             return mISystemUiProxy.screenshot(new Rect(), mDisplaySize.x, mDisplaySize.y, 0, 100000,
-                    false, mDisplayRotation);
+                    false, mDisplayRotation).toBitmap();
         } catch (RemoteException e) {
             Log.e(TAG, "Error capturing snapshot", e);
             return null;
+        } finally {
+            TraceHelper.endSection("TaskSnapshot");
         }
     }
 }
diff --git a/res/layout/all_apps.xml b/res/layout/all_apps.xml
index 832aaef..c42c15c 100644
--- a/res/layout/all_apps.xml
+++ b/res/layout/all_apps.xml
@@ -35,21 +35,27 @@
         android:id="@+id/all_apps_header"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:clickable="true"
-        android:paddingTop="30dp"
+        android:paddingTop="@dimen/all_apps_header_top_padding"
+        android:clipToPadding="false"
         android:layout_below="@id/search_container_all_apps" >
 
         <com.android.launcher3.allapps.PredictionRowView
             android:id="@+id/header_content"
             android:layout_width="match_parent"
-            android:layout_height="wrap_content"/>
+            android:layout_height="wrap_content" />
+
+        <include layout="@layout/all_apps_divider"
+            android:id="@+id/divider"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_alignBottom="@+id/tabs" />
 
         <com.android.launcher3.views.SlidingTabStrip
             android:id="@+id/tabs"
             android:layout_width="match_parent"
             android:layout_height="@dimen/all_apps_header_tab_height"
             android:layout_below="@id/header_content"
-            android:orientation="horizontal">
+            android:orientation="horizontal" >
             <Button
                 android:id="@+id/tab_personal"
                 android:layout_width="0dp"
@@ -67,7 +73,6 @@
                 android:textColor="@color/all_apps_tab_text"
                 android:background="?android:attr/selectableItemBackground"/>
         </com.android.launcher3.views.SlidingTabStrip>
-
     </RelativeLayout>
 
     <!-- Note: we are reusing/repurposing a system attribute for search layout, because of a
diff --git a/res/layout/all_apps_tabs.xml b/res/layout/all_apps_tabs.xml
index fa1d591..54a9b88 100644
--- a/res/layout/all_apps_tabs.xml
+++ b/res/layout/all_apps_tabs.xml
@@ -25,7 +25,7 @@
     android:clipChildren="false"
     android:clipToPadding="false"
     android:descendantFocusability="afterDescendants"
-    android:paddingTop="30dp">
+    android:paddingTop="@dimen/all_apps_header_top_padding">
 
     <include layout="@layout/all_apps_rv_layout" />
 
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index efe5043..266e0b0 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -92,6 +92,8 @@
     <dimen name="all_apps_caret_workspace_offset">18dp</dimen>
     <dimen name="all_apps_header_tab_height">50dp</dimen>
     <dimen name="all_apps_tabs_indicator_height">2dp</dimen>
+    <dimen name="all_apps_header_top_padding">36dp</dimen>
+    <dimen name="all_apps_prediction_row_divider_height">17dp</dimen>
 
 <!-- Search bar in All Apps -->
     <dimen name="all_apps_header_max_elevation">3dp</dimen>
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index efd7b97..2bb95cb 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -281,6 +281,9 @@
                 mAH[i].recyclerView.scrollToTop();
             }
         }
+        if (mFloatingHeaderHandler != null) {
+            mFloatingHeaderHandler.reset();
+        }
         // Reset the search bar and base recycler view after transitioning home
         mSearchUiManager.reset();
     }
@@ -301,6 +304,7 @@
         });
 
         mHeader = findViewById(R.id.all_apps_header);
+        mFloatingHeaderHandler = new FloatingHeaderHandler(mHeader);
         rebindAdapters(mUsingTabs);
 
         mSearchContainer = findViewById(R.id.search_container_all_apps);
@@ -431,10 +435,14 @@
             mAH[AdapterHolder.WORK].setup(mViewPager.getChildAt(1), mWorkMatcher);
             setupWorkProfileTabs();
             setupHeader();
-            mHeader.setVisibility(View.VISIBLE);
         } else {
-            mHeader.setVisibility(View.GONE);
             mAH[AdapterHolder.MAIN].setup(findViewById(R.id.apps_list_view), null);
+            if (FeatureFlags.ALL_APPS_PREDICTION_ROW_VIEW) {
+                setupHeader();
+            } else {
+                mFloatingHeaderHandler = null;
+                mHeader.setVisibility(View.GONE);
+            }
         }
 
         applyTouchDelegate();
@@ -512,7 +520,7 @@
     }
 
     public void setPredictedApps(List<ComponentKeyMapper<AppInfo>> apps) {
-        if (mUsingTabs) {
+        if (mFloatingHeaderHandler != null) {
             mFloatingHeaderHandler.getContentView().setPredictedApps(apps);
         }
         mAH[AdapterHolder.MAIN].appsList.setPredictedApps(apps);
@@ -535,15 +543,24 @@
     }
 
     private void setupHeader() {
+        mHeader.setVisibility(View.VISIBLE);
         int contentHeight = mLauncher.getDeviceProfile().allAppsCellHeightPx;
+        if (!mUsingTabs) {
+            contentHeight += getResources()
+                    .getDimensionPixelSize(R.dimen.all_apps_prediction_row_divider_height);
+        }
         RecyclerView mainRV = mAH[AdapterHolder.MAIN].recyclerView;
-        RecyclerView workRV = mAH[AdapterHolder.WORK] != null
-                ? mAH[AdapterHolder.WORK].recyclerView : null;
-        mFloatingHeaderHandler = new FloatingHeaderHandler(mHeader, mainRV, workRV, contentHeight);
-        mFloatingHeaderHandler.getContentView().setNumAppsPerRow(mNumPredictedAppsPerRow);
-        mFloatingHeaderHandler.getContentView().setComponentToAppMap(mComponentToAppMap);
+        RecyclerView workRV = mAH[AdapterHolder.WORK].recyclerView;
+        mFloatingHeaderHandler.setup(mainRV, workRV, contentHeight);
+        mFloatingHeaderHandler.getContentView().setup(mAH[AdapterHolder.MAIN].adapter,
+                mComponentToAppMap, mNumPredictedAppsPerRow);
+
+        int padding = contentHeight;
+        if (!mUsingTabs) {
+            padding += mHeader.getPaddingTop() + mHeader.getPaddingBottom();
+        }
         for (int i = 0; i < mAH.length; i++) {
-            mAH[i].paddingTopForTabs = contentHeight;
+            mAH[i].paddingTopForTabs = padding;
             mAH[i].applyPadding();
         }
     }
@@ -556,7 +573,9 @@
 
     public void onSearchResultsChanged() {
         for (int i = 0; i < mAH.length; i++) {
-            mAH[i].recyclerView.onSearchResultsChanged();
+            if (mAH[i].recyclerView != null) {
+                mAH[i].recyclerView.onSearchResultsChanged();
+            }
         }
     }
 
@@ -640,9 +659,14 @@
 
         void applyPadding() {
             if (recyclerView != null) {
-                int paddingTop = mUsingTabs ? paddingTopForTabs : padding.top;
+                int paddingTop = mUsingTabs || FeatureFlags.ALL_APPS_PREDICTION_ROW_VIEW
+                        ? paddingTopForTabs : padding.top;
                 recyclerView.setPadding(padding.left, paddingTop, padding.right, padding.bottom);
             }
+            if (mFloatingHeaderHandler != null) {
+                mFloatingHeaderHandler.getContentView()
+                        .setPadding(padding.left, 0 , padding.right, 0);
+            }
         }
 
         void applyNumsPerRow() {
@@ -652,7 +676,7 @@
                 }
                 adapter.setNumAppsPerRow(mNumAppsPerRow);
                 appsList.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow);
-                if (mUsingTabs && mFloatingHeaderHandler != null) {
+                if (mFloatingHeaderHandler != null) {
                     mFloatingHeaderHandler.getContentView()
                             .setNumAppsPerRow(mNumPredictedAppsPerRow);
                 }
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index f9dde2f..7cf2d95 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -460,7 +460,7 @@
         mFastScrollerSections.clear();
         mAdapterItems.clear();
 
-        if (!FeatureFlags.ALL_APPS_TABS_ENABLED) {
+        if (!FeatureFlags.ALL_APPS_PREDICTION_ROW_VIEW) {
             if (DEBUG_PREDICTIONS) {
                 if (mPredictedAppComponents.isEmpty() && !mApps.isEmpty()) {
                     mPredictedAppComponents.add(new ComponentKeyMapper<AppInfo>(new ComponentKey(mApps.get(0).componentName,
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderHandler.java b/src/com/android/launcher3/allapps/FloatingHeaderHandler.java
index 984966b..0b39b7d 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderHandler.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderHandler.java
@@ -15,36 +15,52 @@
  */
 package com.android.launcher3.allapps;
 
+import android.animation.ValueAnimator;
+import android.content.res.Resources;
 import android.graphics.Rect;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.v7.widget.RecyclerView;
 import android.view.View;
+import android.view.ViewGroup;
+import android.widget.RelativeLayout;
 
 import com.android.launcher3.R;
 
-public class FloatingHeaderHandler extends RecyclerView.OnScrollListener {
+public class FloatingHeaderHandler extends RecyclerView.OnScrollListener
+        implements ValueAnimator.AnimatorUpdateListener {
 
-    private final int mMaxTranslation;
     private final View mHeaderView;
-    private final PredictionRowView mContentView;
-    private final RecyclerView mMainRV;
-    private final RecyclerView mWorkRV;
+    private final PredictionRowView mPredictionRow;
+    private final ViewGroup mTabLayout;
+    private final View mDivider;
     private final Rect mClip = new Rect(0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE);
+    private final ValueAnimator mAnimator = ValueAnimator.ofInt(0, 0);
 
+    private RecyclerView mMainRV;
+    private RecyclerView mWorkRV;
+    private boolean mTopOnlyMode;
     private boolean mHeaderHidden;
+    private int mMaxTranslation;
     private int mSnappedScrolledY;
     private int mTranslationY;
     private int mMainScrolledY;
     private int mWorkScrolledY;
     private boolean mMainRVActive;
 
-    public FloatingHeaderHandler(@NonNull View header, @NonNull RecyclerView personalRV,
-            @Nullable RecyclerView workRV, int contentHeight) {
+    public FloatingHeaderHandler(@NonNull ViewGroup header) {
         mHeaderView = header;
-        mContentView = mHeaderView.findViewById(R.id.header_content);
-        mContentView.getLayoutParams().height = contentHeight;
-        mMaxTranslation = contentHeight;
+        mTabLayout = header.findViewById(R.id.tabs);
+        mDivider = header.findViewById(R.id.divider);
+        mPredictionRow = header.findViewById(R.id.header_content);
+    }
+
+    public void setup(@NonNull RecyclerView personalRV, @Nullable RecyclerView workRV,
+        int predictionRowHeight) {
+        mTopOnlyMode = workRV == null;
+        mTabLayout.setVisibility(mTopOnlyMode ? View.GONE : View.VISIBLE);
+        mPredictionRow.getLayoutParams().height = predictionRowHeight;
+        mMaxTranslation = predictionRowHeight;
         mMainRV = personalRV;
         mMainRV.addOnScrollListener(this);
         mWorkRV = workRV;
@@ -52,6 +68,18 @@
             workRV.addOnScrollListener(this);
         }
         setMainActive(true);
+        setupDivider();
+    }
+
+    private void setupDivider() {
+        Resources res = mHeaderView.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);
+        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);
+        mDivider.setLayoutParams(lp);
     }
 
     public void setMainActive(boolean active) {
@@ -65,7 +93,15 @@
     }
 
     public PredictionRowView getContentView() {
-        return mContentView;
+        return mPredictionRow;
+    }
+
+    public ViewGroup getTabLayout() {
+        return mTabLayout;
+    }
+
+    public View getDivider() {
+        return mDivider;
     }
 
     @Override
@@ -75,27 +111,39 @@
             return;
         }
 
+        if (mAnimator.isStarted()) {
+            mAnimator.cancel();
+        }
+
         int current = isMainRV
                 ? (mMainScrolledY -= dy)
                 : (mWorkScrolledY -= dy);
 
-        if (dy == 0) {
-            setExpanded(true);
-        } else {
-            moved(current);
-            apply();
-        }
+        moved(current);
+        apply();
+    }
+
+    public void reset() {
+        mMainScrolledY = 0;
+        mWorkScrolledY = 0;
+        setExpanded(true);
+    }
+
+    private boolean canSnapAt(int currentScrollY) {
+        return !mTopOnlyMode || Math.abs(currentScrollY) <= mPredictionRow.getHeight();
     }
 
     private void moved(final int currentScrollY) {
         if (mHeaderHidden) {
             if (currentScrollY <= mSnappedScrolledY) {
-                mSnappedScrolledY = currentScrollY;
+                if (canSnapAt(currentScrollY)) {
+                    mSnappedScrolledY = currentScrollY;
+                }
             } else {
                 mHeaderHidden = false;
             }
             mTranslationY = currentScrollY;
-        } else {
+        } else if (!mHeaderHidden) {
             mTranslationY = currentScrollY - mSnappedScrolledY - mMaxTranslation;
 
             // update state vars
@@ -110,20 +158,36 @@
     }
 
     private void apply() {
+        int uncappedTranslationY = mTranslationY;
         mTranslationY = Math.max(mTranslationY, -mMaxTranslation);
-        mHeaderView.setTranslationY(mTranslationY);
+        mPredictionRow.setTranslationY(uncappedTranslationY);
+        mTabLayout.setTranslationY(mTranslationY);
+        mDivider.setTranslationY(mTopOnlyMode ? uncappedTranslationY : mTranslationY);
         mClip.top = mMaxTranslation + mTranslationY;
+        // clipping on a draw might cause additional redraw
         mMainRV.setClipBounds(mClip);
         if (mWorkRV != null) {
             mWorkRV.setClipBounds(mClip);
         }
     }
 
+    @Override
+    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
+        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);
+        }
+    }
+
     private void setExpanded(boolean expand) {
         int translateTo = expand ? 0 : -mMaxTranslation;
-        mTranslationY = translateTo;
-        apply();
-
+        mAnimator.setIntValues(mTranslationY, translateTo);
+        mAnimator.addUpdateListener(this);
+        mAnimator.setDuration(150);
+        mAnimator.start();
         mHeaderHidden = !expand;
         mSnappedScrolledY = expand ? getCurrentScroll() - mMaxTranslation : getCurrentScroll();
     }
@@ -136,4 +200,10 @@
         return mMainRVActive ? mMainScrolledY : mWorkScrolledY;
     }
 
+    @Override
+    public void onAnimationUpdate(ValueAnimator animation) {
+        mTranslationY = (Integer) animation.getAnimatedValue();
+        apply();
+    }
+
 }
diff --git a/src/com/android/launcher3/allapps/PredictionRowView.java b/src/com/android/launcher3/allapps/PredictionRowView.java
index 5551f07..45ef6c1 100644
--- a/src/com/android/launcher3/allapps/PredictionRowView.java
+++ b/src/com/android/launcher3/allapps/PredictionRowView.java
@@ -21,9 +21,11 @@
 import android.support.annotation.Nullable;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.view.View;
 import android.widget.LinearLayout;
 
 import com.android.launcher3.AppInfo;
+import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.ComponentKeyMapper;
@@ -43,6 +45,8 @@
     private final List<ComponentKeyMapper<AppInfo>> mPredictedAppComponents = new ArrayList<>();
     // The set of predicted apps resolved from the component names and the current set of apps
     private final List<AppInfo> mPredictedApps = new ArrayList<>();
+    // This adapter is only used to create an identical item w/ same behavior as in the all apps RV
+    private AllAppsGridAdapter mAdapter;
 
     public PredictionRowView(@NonNull Context context) {
         this(context, null);
@@ -53,8 +57,11 @@
         setOrientation(LinearLayout.HORIZONTAL);
     }
 
-    public void setComponentToAppMap(HashMap<ComponentKey, AppInfo> componentToAppMap) {
-        this.mComponentToAppMap = componentToAppMap;
+    public void setup(AllAppsGridAdapter adapter,
+            HashMap<ComponentKey, AppInfo> componentToAppMap, int numPredictedAppsPerRow) {
+        mAdapter = adapter;
+        mComponentToAppMap = componentToAppMap;
+        mNumPredictedAppsPerRow = numPredictedAppsPerRow;
     }
 
     /**
@@ -64,10 +71,6 @@
         mNumPredictedAppsPerRow = numPredictedAppsPerRow;
     }
 
-    public void onAppsUpdated() {
-        // TODO
-    }
-
     /**
      * Returns the predicted apps.
      */
@@ -87,15 +90,35 @@
     public void setPredictedApps(List<ComponentKeyMapper<AppInfo>> apps) {
         mPredictedAppComponents.clear();
         mPredictedAppComponents.addAll(apps);
+        mPredictedApps.addAll(processPredictedAppComponents(mPredictedAppComponents));
+        onAppsUpdated();
+    }
 
-        List<AppInfo> newPredictedApps = processPredictedAppComponents(apps);
-        // We only need to do work if any of the visible predicted apps have changed.
-        if (!newPredictedApps.equals(mPredictedApps)) {
-            if (newPredictedApps.size() == mPredictedApps.size()) {
-                swapInNewPredictedApps(newPredictedApps);
+    private void onAppsUpdated() {
+        if (getChildCount() != mNumPredictedAppsPerRow) {
+            while (getChildCount() > mNumPredictedAppsPerRow) {
+                removeViewAt(0);
+            }
+            while (getChildCount() < mNumPredictedAppsPerRow) {
+                AllAppsGridAdapter.ViewHolder holder = mAdapter
+                        .onCreateViewHolder(this, AllAppsGridAdapter.VIEW_TYPE_ICON);
+                BubbleTextView icon = (BubbleTextView) holder.itemView;
+                LinearLayout.LayoutParams params =
+                        new LayoutParams(0, icon.getLayoutParams().height);
+                params.weight = 1;
+                icon.setLayoutParams(params);
+                addView(icon);
+            }
+        }
+
+        for (int i = 0; i < getChildCount(); i++) {
+            BubbleTextView icon = (BubbleTextView) getChildAt(i);
+            icon.reset();
+            if (mPredictedApps.size() > i) {
+                icon.setVisibility(View.VISIBLE);
+                icon.applyFromApplicationInfo(mPredictedApps.get(i));
             } else {
-                // We need to update the appIndex of all the items.
-                onAppsUpdated();
+                icon.setVisibility(View.INVISIBLE);
             }
         }
     }
@@ -124,16 +147,4 @@
         }
         return predictedApps;
     }
-
-    /**
-     * Swaps out the old predicted apps with the new predicted apps, in place. This optimization
-     * allows us to skip an entire relayout that would otherwise be called by notifyDataSetChanged.
-     *
-     * Note: This should only be called if the # of predicted apps is the same.
-     *       This method assumes that predicted apps are the first items in the adapter.
-     */
-    private void swapInNewPredictedApps(List<AppInfo> apps) {
-        // TODO
-    }
-
 }
diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java
index 1924710..7cf3da0 100644
--- a/src/com/android/launcher3/config/BaseFlags.java
+++ b/src/com/android/launcher3/config/BaseFlags.java
@@ -61,4 +61,6 @@
 
     // When enabled shows a work profile tab in all apps
     public static final boolean ALL_APPS_TABS_ENABLED = false;
+    // When enabled prediction row is rendered as it's own custom view
+    public static final boolean ALL_APPS_PREDICTION_ROW_VIEW = false;
 }
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index 816c1d4..8640401 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -118,9 +118,9 @@
         deepShortcutMap.clear();
     }
 
-     public synchronized void dump(String prefix, FileDescriptor fd, PrintWriter writer,
-             String[] args) {
-        if (args.length > 0 && TextUtils.equals(args[0], "--proto")) {
+    public synchronized void dump(String prefix, FileDescriptor fd, PrintWriter writer,
+            String[] args) {
+        if (Arrays.asList(args).contains("--proto")) {
             dumpProto(prefix, fd, writer, args);
             return;
         }
@@ -219,7 +219,7 @@
             targetList.addAll(workspaces.valueAt(i).getFlattenedList());
         }
 
-        if (args.length > 1 && TextUtils.equals(args[1], "--debug")) {
+        if (Arrays.asList(args).contains("--debug")) {
             for (int i = 0; i < targetList.size(); i++) {
                 writer.println(prefix + DumpTargetWrapper.getDumpTargetStr(targetList.get(i)));
             }
diff --git a/src/com/android/launcher3/util/TraceHelper.java b/src/com/android/launcher3/util/TraceHelper.java
index 5b66fcd..0f3ac57 100644
--- a/src/com/android/launcher3/util/TraceHelper.java
+++ b/src/com/android/launcher3/util/TraceHelper.java
@@ -33,9 +33,8 @@
 
     private static final boolean ENABLED = FeatureFlags.IS_DOGFOOD_BUILD;
 
-    private static final boolean SYSTEM_TRACE = true;
-    private static final ArrayMap<String, MutableLong> sUpTimes =
-            ENABLED ? new ArrayMap<String, MutableLong>() : null;
+    private static final boolean SYSTEM_TRACE = false;
+    private static final ArrayMap<String, MutableLong> sUpTimes = ENABLED ? new ArrayMap<>() : null;
 
     public static void beginSection(String sectionName) {
         if (ENABLED) {