Merge "Import translations. DO NOT MERGE" into ub-launcher3-master
diff --git a/build.gradle b/build.gradle
index 61c05e5..0030b8b 100644
--- a/build.gradle
+++ b/build.gradle
@@ -13,12 +13,12 @@
 apply plugin: 'com.google.protobuf'
 
 android {
-    compileSdkVersion 27
-    buildToolsVersion '27.0.0'
+    compileSdkVersion 28
+    buildToolsVersion '28.0.0'
 
     defaultConfig {
         minSdkVersion 21
-        targetSdkVersion 27
+        targetSdkVersion 28
         versionCode 1
         versionName "1.0"
 
@@ -102,7 +102,7 @@
     jcenter()
 }
 
-final String SUPPORT_LIBS_VERSION = '27.0.0-SNAPSHOT'
+final String SUPPORT_LIBS_VERSION = '28.0.0-SNAPSHOT'
 dependencies {
     compile "com.android.support:support-v4:${SUPPORT_LIBS_VERSION}"
     compile "com.android.support:support-dynamic-animation:${SUPPORT_LIBS_VERSION}"
diff --git a/proguard.flags b/proguard.flags
index 987fb6f..b8cade5 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -97,6 +97,11 @@
 # support jar.
 -keep class android.support.v7.widget.RecyclerView { *; }
 
+# LauncherAppTransitionManager
+-keep class com.android.launcher3.LauncherAppTransitionManagerImpl {
+    public <init>(...);
+}
+
 -keep interface com.android.launcher3.userevent.nano.LauncherLogProto.** {
   *;
 }
diff --git a/res/layout/predictions_view.xml b/quickstep/res/values/config.xml
similarity index 64%
copy from res/layout/predictions_view.xml
copy to quickstep/res/values/config.xml
index 280290c..94211c6 100644
--- a/res/layout/predictions_view.xml
+++ b/quickstep/res/values/config.xml
@@ -1,11 +1,11 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
+<!-- Copyright (C) 2018 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
+        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,
@@ -13,7 +13,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<com.android.launcher3.allapps.PredictionRowView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content" />
\ No newline at end of file
+<resources>
+    <string name="task_overlay_factory_class" translatable="false"></string>
+
+</resources>
diff --git a/res/layout/predictions_view.xml b/quickstep/res/values/override.xml
similarity index 69%
rename from res/layout/predictions_view.xml
rename to quickstep/res/values/override.xml
index 280290c..ba99d81 100644
--- a/res/layout/predictions_view.xml
+++ b/quickstep/res/values/override.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
+<!-- Copyright (C) 2018 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
@@ -13,7 +13,8 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<com.android.launcher3.allapps.PredictionRowView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content" />
\ No newline at end of file
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string name="app_transition_manager_class" translatable="false">com.android.launcher3.LauncherAppTransitionManagerImpl</string>
+</resources>
+
diff --git a/quickstep/src/com/android/launcher3/LauncherAppTransitionManager.java b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
similarity index 78%
rename from quickstep/src/com/android/launcher3/LauncherAppTransitionManager.java
rename to quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
index 47179c5..9968ca7 100644
--- a/quickstep/src/com/android/launcher3/LauncherAppTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
@@ -26,6 +26,8 @@
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Matrix;
@@ -55,11 +57,15 @@
 /**
  * Manages the opening and closing app transitions from Launcher.
  */
-public class LauncherAppTransitionManager {
+public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManager {
 
     private static final String TAG = "LauncherTransition";
     private static final int REFRESH_RATE_MS = 16;
 
+    private static final String CONTROL_REMOTE_APP_TRANSITION_PERMISSION =
+            "android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS";
+
+    private static final int LAUNCHER_RESUME_START_DELAY = 150;
     private static final int CLOSING_TRANSITION_DURATION_MS = 350;
 
     // Progress = 0: All apps is fully pulled up, Progress = 1: All apps is fully pulled down.
@@ -76,60 +82,79 @@
     private ImageView mFloatingView;
     private boolean mIsRtl;
 
-    public LauncherAppTransitionManager(Launcher launcher) {
-        mLauncher = launcher;
-        mDragLayer = launcher.getDragLayer();
-        mDeviceProfile = launcher.getDeviceProfile();
+    private Animator mCurrentAnimator;
 
-        mIsRtl = Utilities.isRtl(launcher.getResources());
+    public LauncherAppTransitionManagerImpl(Context context) {
+        mLauncher = Launcher.getLauncher(context);
+        mDragLayer = mLauncher.getDragLayer();
+        mDeviceProfile = mLauncher.getDeviceProfile();
 
-        Resources res = launcher.getResources();
+        mIsRtl = Utilities.isRtl(mLauncher.getResources());
+
+        Resources res = mLauncher.getResources();
         mContentTransY = res.getDimensionPixelSize(R.dimen.content_trans_y);
         mWorkspaceTransY = res.getDimensionPixelSize(R.dimen.workspace_trans_y);
     }
 
+    private void setCurrentAnimator(Animator animator) {
+        if (mCurrentAnimator != null && mCurrentAnimator.isRunning()) {
+            mCurrentAnimator.cancel();
+        }
+        mCurrentAnimator = animator;
+    }
+
     /**
      * @return A Bundle with remote animations that controls how the window of the opening
      *         targets are displayed.
      */
-    public Bundle getActivityLauncherOptions(View v) {
-        RemoteAnimationRunnerCompat runner = new LauncherAnimationRunner(mLauncher) {
-            @Override
-            public void onAnimationStart(RemoteAnimationTargetCompat[] targets,
-                    Runnable finishedCallback) {
-                // Post at front of queue ignoring sync barriers to make sure it gets processed
-                // before the next frame.
-                postAtFrontOfQueueAsynchronously(v.getHandler(), () -> {
-                    mAnimator = new AnimatorSet();
-                    mAnimator.play(getLauncherAnimators(v));
-                    mAnimator.play(getWindowAnimators(v, targets));
-                    mAnimator.addListener(new AnimatorListenerAdapter() {
-                        @Override
-                        public void onAnimationEnd(Animator animation) {
-                            // Reset launcher to normal state
-                            v.setVisibility(View.VISIBLE);
-                            ((ViewGroup) mDragLayer.getParent()).removeView(mFloatingView);
+    @Override
+    public Bundle getActivityLaunchOptions(Launcher launcher, View v) {
+        if (hasControlRemoteAppTransitionPermission()) {
+            try {
+                RemoteAnimationRunnerCompat runner = new LauncherAnimationRunner(mLauncher) {
+                    @Override
+                    public void onAnimationStart(RemoteAnimationTargetCompat[] targets,
+                                                 Runnable finishedCallback) {
+                        // Post at front of queue ignoring sync barriers to make sure it gets
+                        // processed before the next frame.
+                        postAtFrontOfQueueAsynchronously(v.getHandler(), () -> {
+                            mAnimator = new AnimatorSet();
+                            setCurrentAnimator(mAnimator);
+                            mAnimator.play(getLauncherAnimators(v));
+                            mAnimator.play(getWindowAnimators(v, targets));
+                            mAnimator.addListener(new AnimatorListenerAdapter() {
+                                @Override
+                                public void onAnimationEnd(Animator animation) {
+                                    // Reset launcher to normal state
+                                    v.setVisibility(View.VISIBLE);
+                                    ((ViewGroup) mDragLayer.getParent()).removeView(mFloatingView);
 
-                            mDragLayer.setAlpha(1f);
-                            mDragLayer.setTranslationY(0f);
+                                    mDragLayer.setAlpha(1f);
+                                    mDragLayer.setTranslationY(0f);
 
-                            View appsView = mLauncher.getAppsView();
-                            appsView.setAlpha(1f);
-                            appsView.setTranslationY(0f);
+                                    View appsView = mLauncher.getAppsView();
+                                    appsView.setAlpha(1f);
+                                    appsView.setTranslationY(0f);
 
-                            finishedCallback.run();
-                        }
-                    });
-                    mAnimator.start();
-                    // Because t=0 has the app icon in its original spot, we can skip the first
-                    // frame and have the same movement one frame earlier.
-                    mAnimator.setCurrentPlayTime(REFRESH_RATE_MS);
-                });
+                                    finishedCallback.run();
+                                }
+                            });
+                            mAnimator.start();
+                            // Because t=0 has the app icon in its original spot, we can skip the
+                            // first frame and have the same movement one frame earlier.
+                            mAnimator.setCurrentPlayTime(REFRESH_RATE_MS);
+                        });
+                    }
+                };
+
+                return ActivityOptionsCompat.makeRemoteAnimation(
+                        new RemoteAnimationAdapterCompat(runner, 500, 380)).toBundle();
+            } catch (NoClassDefFoundError e) {
+                // Gracefully fall back to default launch options if the user's platform doesn't
+                // have the latest changes.
             }
-        };
-
-        return ActivityOptionsCompat.makeRemoteAnimation(
-                new RemoteAnimationAdapterCompat(runner, 500, 380)).toBundle();
+        }
+        return getDefaultActivityLaunchOptions(launcher, v);
     }
 
     /**
@@ -149,7 +174,7 @@
      *             Else: Animate the content so that it moves downwards and fades out.
      */
     private AnimatorSet getLauncherContentAnimator(boolean show) {
-        AnimatorSet hideLauncher = new AnimatorSet();
+        AnimatorSet launcherAnimator = new AnimatorSet();
 
         float[] alphas = show
                 ? new float[] {0, 1}
@@ -161,6 +186,9 @@
         if (mLauncher.isInState(LauncherState.ALL_APPS) && !mDeviceProfile.isVerticalBarLayout()) {
             // All Apps in portrait mode is full screen, so we only animate AllAppsContainerView.
             View appsView = mLauncher.getAppsView();
+            appsView.setAlpha(alphas[0]);
+            appsView.setTranslationY(trans[0]);
+
             ObjectAnimator alpha = ObjectAnimator.ofFloat(appsView, View.ALPHA, alphas);
             alpha.setDuration(217);
             alpha.setInterpolator(Interpolators.LINEAR);
@@ -168,9 +196,12 @@
             transY.setInterpolator(Interpolators.AGGRESSIVE_EASE);
             transY.setDuration(350);
 
-            hideLauncher.play(alpha);
-            hideLauncher.play(transY);
+            launcherAnimator.play(alpha);
+            launcherAnimator.play(transY);
         } else {
+            mDragLayer.setAlpha(alphas[0]);
+            mDragLayer.setTranslationY(trans[0]);
+
             ObjectAnimator dragLayerAlpha = ObjectAnimator.ofFloat(mDragLayer, View.ALPHA, alphas);
             dragLayerAlpha.setDuration(217);
             dragLayerAlpha.setInterpolator(Interpolators.LINEAR);
@@ -179,10 +210,10 @@
             dragLayerTransY.setInterpolator(Interpolators.AGGRESSIVE_EASE);
             dragLayerTransY.setDuration(350);
 
-            hideLauncher.play(dragLayerAlpha);
-            hideLauncher.play(dragLayerTransY);
+            launcherAnimator.play(dragLayerAlpha);
+            launcherAnimator.play(dragLayerTransY);
         }
-        return hideLauncher;
+        return launcherAnimator;
     }
 
     /**
@@ -361,15 +392,22 @@
     /**
      * Registers remote animations used when closing apps to home screen.
      */
+    @Override
     public void registerRemoteAnimations() {
-        RemoteAnimationDefinitionCompat definition = new RemoteAnimationDefinitionCompat();
-        definition.addRemoteAnimation(WindowManagerWrapper.TRANSIT_WALLPAPER_OPEN,
-                new RemoteAnimationAdapterCompat(getWallpaperOpenRunner(), 0,
-                        CLOSING_TRANSITION_DURATION_MS));
+        if (hasControlRemoteAppTransitionPermission()) {
+            try {
+                RemoteAnimationDefinitionCompat definition = new RemoteAnimationDefinitionCompat();
+                definition.addRemoteAnimation(WindowManagerWrapper.TRANSIT_WALLPAPER_OPEN,
+                        new RemoteAnimationAdapterCompat(getWallpaperOpenRunner(), 0,
+                                CLOSING_TRANSITION_DURATION_MS));
 
 //      TODO: App controlled transition for unlock to home TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER
 
-        new ActivityCompat(mLauncher).registerRemoteAnimations(definition);
+                new ActivityCompat(mLauncher).registerRemoteAnimations(definition);
+            } catch (NoClassDefFoundError e) {
+                // Gracefully fall back if the user's platform doesn't have the latest changes
+            }
+        }
     }
 
     /**
@@ -385,11 +423,13 @@
                 postAtFrontOfQueueAsynchronously(handler, () -> {
                     // We use a separate transition for Overview mode.
                     if (mLauncher.isInState(LauncherState.OVERVIEW)) {
+                        setCurrentAnimator(null);
                         finishedCallback.run();
                         return;
                     }
 
                     mAnimator = new AnimatorSet();
+                    setCurrentAnimator(mAnimator);
                     mAnimator.addListener(new AnimatorListenerAdapter() {
                         @Override
                         public void onAnimationEnd(Animator animation) {
@@ -465,7 +505,9 @@
     private AnimatorSet getLauncherResumeAnimation() {
         if (mLauncher.isInState(LauncherState.ALL_APPS)
                 || mLauncher.getDeviceProfile().isVerticalBarLayout()) {
-            return getLauncherContentAnimator(true /* show */);
+            AnimatorSet contentAnimator = getLauncherContentAnimator(true /* show */);
+            contentAnimator.setStartDelay(LAUNCHER_RESUME_START_DELAY);
+            return contentAnimator;
         } else {
             AnimatorSet workspaceAnimator = new AnimatorSet();
             mLauncher.getWorkspace().setTranslationY(mWorkspaceTransY);
@@ -474,7 +516,7 @@
                     View.TRANSLATION_Y, mWorkspaceTransY, 0));
             workspaceAnimator.play(ObjectAnimator.ofFloat(mLauncher.getWorkspace(), View.ALPHA,
                     0, 1f));
-            workspaceAnimator.setStartDelay(150);
+            workspaceAnimator.setStartDelay(LAUNCHER_RESUME_START_DELAY);
             workspaceAnimator.setDuration(333);
             workspaceAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
 
@@ -489,7 +531,7 @@
 
             Animator allAppsSlideIn =
                     ObjectAnimator.ofFloat(allAppsController, ALL_APPS_PROGRESS, startY, slideEnd);
-            allAppsSlideIn.setStartDelay(150);
+            allAppsSlideIn.setStartDelay(LAUNCHER_RESUME_START_DELAY);
             allAppsSlideIn.setDuration(317);
             allAppsSlideIn.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
 
@@ -505,6 +547,11 @@
         }
     }
 
+    private boolean hasControlRemoteAppTransitionPermission() {
+        return mLauncher.checkSelfPermission(CONTROL_REMOTE_APP_TRANSITION_PERMISSION)
+                == PackageManager.PERMISSION_GRANTED;
+    }
+
     /**
      * Helper method that allows us to get interpolated values for embedded
      * animations with a delay and/or different duration.
diff --git a/quickstep/src/com/android/launcher3/uioverrides/EdgeSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/EdgeSwipeController.java
index 356a144..fa09f23 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/EdgeSwipeController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/EdgeSwipeController.java
@@ -26,6 +26,7 @@
 import android.graphics.Rect;
 import android.view.MotionEvent;
 
+import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.anim.SpringAnimationHandler;
 import com.android.launcher3.dragndrop.DragLayer;
@@ -56,11 +57,7 @@
 
     @Override
     protected boolean isTransitionFlipped() {
-        if (mLauncher.getDeviceProfile().isVerticalBarLayout()) {
-            Rect insets = mLauncher.getDragLayer().getInsets();
-            return insets.left > insets.right;
-        }
-        return false;
+        return mLauncher.getDeviceProfile().isSeascape();
     }
 
     @Override
@@ -82,9 +79,10 @@
         RecentsView.getPageRect(launcher.getDeviceProfile(), launcher, sTempRect);
         DragLayer dl = launcher.getDragLayer();
         Rect insets = dl.getInsets();
+        DeviceProfile dp = launcher.getDeviceProfile();
 
-        if (launcher.getDeviceProfile().isVerticalBarLayout()) {
-            if (insets.left > insets.right) {
+        if (dp.isVerticalBarLayout()) {
+            if (dp.isSeascape()) {
                 return insets.left + sTempRect.left;
             } else {
                 return dl.getWidth() - sTempRect.right + insets.right;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
index a004dac..b4f40c2 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
@@ -25,7 +25,6 @@
 import android.view.View.AccessibilityDelegate;
 
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppTransitionManager;
 import com.android.launcher3.LauncherStateManager.StateHandler;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.graphics.BitmapRenderer;
@@ -84,31 +83,4 @@
         RecentsView recents = launcher.getOverviewPanel();
         recents.reset();
     }
-
-    private static boolean hasControlRemoteAppTransitionPermission(Launcher launcher) {
-        return launcher.checkSelfPermission(CONTROL_REMOTE_APP_TRANSITION_PERMISSION)
-                == PackageManager.PERMISSION_GRANTED;
-    }
-
-    public static Bundle getActivityLaunchOptions(Launcher launcher, View v) {
-        if (hasControlRemoteAppTransitionPermission(launcher)) {
-            try {
-                return new LauncherAppTransitionManager(launcher).getActivityLauncherOptions(v);
-            } catch (NoClassDefFoundError e) {
-                // Gracefully fall back to default launch options if the user's platform doesn't
-                // have the latest changes.
-            }
-        }
-        return launcher.getDefaultActivityLaunchOptions(v);
-    }
-
-    public static void registerRemoteAnimations(Launcher launcher) {
-        if (hasControlRemoteAppTransitionPermission(launcher)) {
-            try {
-                new LauncherAppTransitionManager(launcher).registerRemoteAnimations();
-            } catch (NoClassDefFoundError e) {
-                // Gracefully fall back if the user's platform doesn't have the latest changes
-            }
-        }
-    }
 }
diff --git a/quickstep/src/com/android/quickstep/RecentsView.java b/quickstep/src/com/android/quickstep/RecentsView.java
index 31f8058..b35d31b 100644
--- a/quickstep/src/com/android/quickstep/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/RecentsView.java
@@ -176,7 +176,7 @@
 
         if (dp.isVerticalBarLayout()) {
             boolean wasScrimOnLeft = mScrimOnLeft;
-            mScrimOnLeft = insets.left > insets.right;
+            mScrimOnLeft = dp.isSeascape();
 
             if (mScrim == null || wasScrimOnLeft != mScrimOnLeft) {
                 Drawable scrim = getContext().getDrawable(mScrimOnLeft
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
new file mode 100644
index 0000000..c2fb7be
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep;
+
+import android.content.Context;
+import android.graphics.Matrix;
+import android.view.View;
+
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.util.Preconditions;
+import com.android.systemui.shared.recents.model.ThumbnailData;
+
+/**
+ * Factory class to create and add an overlays on the TaskView
+ */
+public class TaskOverlayFactory {
+
+    private static TaskOverlayFactory sInstance;
+
+    public static TaskOverlayFactory get(Context context) {
+        Preconditions.assertUIThread();
+        if (sInstance == null) {
+            sInstance = Utilities.getOverrideObject(TaskOverlayFactory.class,
+                    context.getApplicationContext(), R.string.task_overlay_factory_class);
+        }
+        return sInstance;
+    }
+
+    public TaskOverlay createOverlay(View thumbnailView) {
+        return new TaskOverlay();
+    }
+
+    public static class TaskOverlay {
+
+        public void setTaskInfo(ThumbnailData thumbnail, Matrix matrix) { }
+
+        public void reset() { }
+
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/TaskThumbnailView.java
index 87dec67..36a0601 100644
--- a/quickstep/src/com/android/quickstep/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/TaskThumbnailView.java
@@ -28,7 +28,6 @@
 import android.graphics.Matrix;
 import android.graphics.Paint;
 import android.graphics.PorterDuff.Mode;
-import android.graphics.Rect;
 import android.graphics.Shader;
 import android.util.AttributeSet;
 import android.view.View;
@@ -36,6 +35,7 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
+import com.android.quickstep.TaskOverlayFactory.TaskOverlay;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 
@@ -49,15 +49,14 @@
     private final float mCornerRadius;
     private final float mFadeLength;
 
+    private final TaskOverlay mOverlay;
     private final Paint mPaint = new Paint();
 
     private final Matrix mMatrix = new Matrix();
-    private final Rect mThumbnailRect = new Rect();
 
     private ThumbnailData mThumbnailData;
     protected BitmapShader mBitmapShader;
 
-    private float mThumbnailScale;
     private float mDimAlpha = 1f;
 
     public TaskThumbnailView(Context context) {
@@ -72,6 +71,11 @@
         super(context, attrs, defStyleAttr);
         mCornerRadius = getResources().getDimension(R.dimen.task_corner_radius);
         mFadeLength = getResources().getDimension(R.dimen.task_fade_length);
+        mOverlay = TaskOverlayFactory.get(context).createOverlay(this);
+    }
+
+    public void bind() {
+        mOverlay.reset();
     }
 
     /**
@@ -83,19 +87,15 @@
         if (thumbnailData != null && thumbnailData.thumbnail != null) {
             Bitmap bm = thumbnailData.thumbnail;
             bm.prepareToDraw();
-            mThumbnailScale = thumbnailData.scale;
             mBitmapShader = new BitmapShader(bm, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
             mPaint.setShader(mBitmapShader);
-            mThumbnailRect.set(0, 0,
-                    bm.getWidth() - thumbnailData.insets.left - thumbnailData.insets.right,
-                    bm.getHeight() - thumbnailData.insets.top - thumbnailData.insets.bottom);
             mThumbnailData = thumbnailData;
             updateThumbnailMatrix();
         } else {
             mBitmapShader = null;
             mThumbnailData = null;
             mPaint.setShader(null);
-            mThumbnailRect.setEmpty();
+            mOverlay.reset();
         }
         updateThumbnailPaintFilter();
     }
@@ -126,36 +126,41 @@
     }
 
     private void updateThumbnailMatrix() {
-        mThumbnailScale = 1f;
         if (mBitmapShader != null && mThumbnailData != null) {
+            float scale = mThumbnailData.scale;
+            float thumbnailWidth = mThumbnailData.thumbnail.getWidth() -
+                    (mThumbnailData.insets.left + mThumbnailData.insets.right) * scale;
+            float thumbnailHeight = mThumbnailData.thumbnail.getHeight() -
+                    (mThumbnailData.insets.top + mThumbnailData.insets.bottom) * scale;
+            final float thumbnailScale;
+
             if (getMeasuredWidth() == 0) {
                 // If we haven't measured , skip the thumbnail drawing and only draw the background
                 // color
-                mThumbnailScale = 0f;
+                thumbnailScale = 0f;
             } else {
-                float invThumbnailScale = 1f / mThumbnailScale;
                 final Configuration configuration =
                         getContext().getApplicationContext().getResources().getConfiguration();
                 final DeviceProfile profile = Launcher.getLauncher(getContext()).getDeviceProfile();
                 if (configuration.orientation == mThumbnailData.orientation) {
                     // If we are in the same orientation as the screenshot, just scale it to the
                     // width of the task view
-                    mThumbnailScale = (float) getMeasuredWidth() / mThumbnailRect.width();
+                    thumbnailScale = getMeasuredWidth() / thumbnailWidth;
                 } else if (configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
                     // Scale the landscape thumbnail up to app size, then scale that to the task
                     // view size to match other portrait screenshots
-                    mThumbnailScale = invThumbnailScale *
-                            ((float) getMeasuredWidth() / profile.widthPx);
+                    thumbnailScale = ((float) getMeasuredWidth() / profile.widthPx);
                 } else {
                     // Otherwise, scale the screenshot to fit 1:1 in the current orientation
-                    mThumbnailScale = invThumbnailScale;
+                    thumbnailScale = 1;
                 }
             }
-            mMatrix.setTranslate(-mThumbnailData.insets.left, -mThumbnailData.insets.top);
-            mMatrix.postScale(mThumbnailScale, mThumbnailScale);
+            mMatrix.setTranslate(-mThumbnailData.insets.left * scale,
+                    -mThumbnailData.insets.top * scale);
+            mMatrix.postScale(thumbnailScale, thumbnailScale);
             mBitmapShader.setLocalMatrix(mMatrix);
 
-            float bitmapHeight = Math.max(mThumbnailRect.height() * mThumbnailScale, 0);
+            float bitmapHeight = Math.max(thumbnailHeight * thumbnailScale, 0);
             Shader shader = mBitmapShader;
             if (bitmapHeight < getMeasuredHeight()) {
                 int color = mPaint.getColor();
@@ -165,7 +170,7 @@
                 shader = new ComposeShader(fade, shader, Mode.DST_OVER);
             }
 
-            float bitmapWidth = Math.max(mThumbnailRect.width() * mThumbnailScale, 0);
+            float bitmapWidth = Math.max(thumbnailWidth * thumbnailScale, 0);
             if (bitmapWidth < getMeasuredWidth()) {
                 int color = mPaint.getColor();
                 LinearGradient fade = new LinearGradient(
@@ -175,6 +180,8 @@
             }
             mPaint.setShader(shader);
         }
+
+        mOverlay.setTaskInfo(mThumbnailData, mMatrix);
         invalidate();
     }
 
diff --git a/quickstep/src/com/android/quickstep/TaskView.java b/quickstep/src/com/android/quickstep/TaskView.java
index 0e999f8..46fcc72 100644
--- a/quickstep/src/com/android/quickstep/TaskView.java
+++ b/quickstep/src/com/android/quickstep/TaskView.java
@@ -36,6 +36,7 @@
 import com.android.launcher3.R;
 import com.android.quickstep.RecentsView.PageCallbacks;
 import com.android.quickstep.RecentsView.ScrollState;
+import com.android.quickstep.TaskOverlayFactory.TaskOverlay;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.Task.TaskCallbacks;
 import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -113,6 +114,7 @@
             mTask.removeCallback(this);
         }
         mTask = task;
+        mSnapshotView.bind();
         task.addCallback(this);
     }
 
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index fe18703..509ffa9 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -185,6 +185,7 @@
     @Override
     public void onDestroy() {
         sConnected = false;
+        sQuickScrubEnabled = false;
         super.onDestroy();
     }
 
diff --git a/res/layout/all_apps.xml b/res/layout/all_apps.xml
index 8cf32bd..2ce6b8c 100644
--- a/res/layout/all_apps.xml
+++ b/res/layout/all_apps.xml
@@ -31,48 +31,7 @@
 
     <include layout="@layout/all_apps_fast_scroller" />
 
-    <com.android.launcher3.allapps.FloatingHeaderView
-        android:id="@+id/all_apps_header"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:paddingTop="@dimen/all_apps_header_top_padding"
-        android:clipToPadding="false"
-        android:layout_below="@id/search_container_all_apps" >
-
-        <include layout="@layout/predictions_view" android:id="@+id/header_content" />
-
-        <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
-                android:id="@+id/tab_personal"
-                android:layout_width="0dp"
-                android:layout_height="match_parent"
-                android:layout_weight="1"
-                android:background="?android:attr/selectableItemBackground"
-                android:fontFamily="sans-serif-medium"
-                android:text="@string/all_apps_personal_tab"
-                android:textAllCaps="true"
-                android:textColor="@color/all_apps_tab_text"
-                android:textSize="14sp"/>
-            <Button
-                android:id="@+id/tab_work"
-                android:layout_width="0dp"
-                android:layout_height="match_parent"
-                android:layout_weight="1"
-                android:background="?android:attr/selectableItemBackground"
-                android:fontFamily="sans-serif-medium"
-                android:text="@string/all_apps_work_tab"
-                android:textAllCaps="true"
-                android:textColor="@color/all_apps_work_tab_text"
-                android:textSize="14sp"/>
-        </com.android.launcher3.allapps.PersonalWorkSlidingTabStrip>
-    </com.android.launcher3.allapps.FloatingHeaderView>
+    <include layout="@layout/all_apps_floating_header" />
 
     <!-- Note: we are reusing/repurposing a system attribute for search layout, because of a
      platform bug, which prevents using custom attributes in <include> tag -->
diff --git a/res/layout/all_apps_floating_header.xml b/res/layout/all_apps_floating_header.xml
new file mode 100644
index 0000000..166725d
--- /dev/null
+++ b/res/layout/all_apps_floating_header.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<com.android.launcher3.allapps.FloatingHeaderView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/all_apps_header"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_below="@id/search_container_all_apps"
+    android:clipToPadding="false"
+    android:paddingTop="@dimen/all_apps_header_top_padding"
+    android:orientation="vertical" >
+
+    <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:orientation="horizontal">
+
+        <Button
+            android:id="@+id/tab_personal"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:background="?android:attr/selectableItemBackground"
+            android:fontFamily="sans-serif-medium"
+            android:text="@string/all_apps_personal_tab"
+            android:textAllCaps="true"
+            android:textColor="@color/all_apps_tab_text"
+            android:textSize="14sp" />
+
+        <Button
+            android:id="@+id/tab_work"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:background="?android:attr/selectableItemBackground"
+            android:fontFamily="sans-serif-medium"
+            android:text="@string/all_apps_work_tab"
+            android:textAllCaps="true"
+            android:textColor="@color/all_apps_work_tab_text"
+            android:textSize="14sp" />
+    </com.android.launcher3.allapps.PersonalWorkSlidingTabStrip>
+</com.android.launcher3.allapps.FloatingHeaderView>
diff --git a/res/values/config.xml b/res/values/config.xml
index 2096200..3dddac2 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -92,6 +92,9 @@
     <!-- Name of a user event dispatcher class. -->
     <string name="user_event_dispatcher_class" translatable="false"></string>
 
+    <!-- Name of an app transition manager class. -->
+    <string name="app_transition_manager_class" translatable="false"></string>
+
     <!-- Name of a color extraction implementation class. -->
     <string name="color_extraction_impl_class" translatable="false"></string>
 
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 164efe5..5789755 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -412,7 +412,7 @@
             padding.bottom = edgeMarginPx;
             padding.left = hotseatBarSidePaddingPx;
             padding.right = hotseatBarSidePaddingPx;
-            if (mInsets.left > mInsets.right) {
+            if (isSeascape()) {
                 padding.left += hotseatBarSizePx;
                 padding.right += pageIndicatorSizePx;
             } else {
@@ -480,6 +480,11 @@
         return isLandscape && transposeLayoutWithOrientation;
     }
 
+    public boolean isSeascape() {
+        // TODO: This might not hold true for multi window mode, use configuration insead.
+        return isVerticalBarLayout() && mInsets.left > mInsets.right;
+    }
+
     public boolean shouldFadeAdjacentWorkspaceScreens() {
         return isVerticalBarLayout() || isLargeTablet;
     }
diff --git a/src/com/android/launcher3/DropTargetBar.java b/src/com/android/launcher3/DropTargetBar.java
index d78043d..cc6a58f 100644
--- a/src/com/android/launcher3/DropTargetBar.java
+++ b/src/com/android/launcher3/DropTargetBar.java
@@ -93,8 +93,8 @@
         if (grid.isVerticalBarLayout()) {
             lp.width = grid.dropTargetBarSizePx;
             lp.height = grid.availableHeightPx - 2 * grid.edgeMarginPx;
-            lp.gravity = insets.left > insets.right ? Gravity.RIGHT : Gravity.LEFT;
-            tooltipLocation = insets.left > insets.right ? TOOLTIP_LEFT : TOOLTIP_RIGHT;
+            lp.gravity = grid.isSeascape() ? Gravity.RIGHT : Gravity.LEFT;
+            tooltipLocation = grid.isSeascape() ? TOOLTIP_LEFT : TOOLTIP_RIGHT;
         } else {
             int gap;
             if (grid.isTablet) {
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index 25eacb5..9d2bb07 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -164,7 +164,7 @@
             mContent.setGridSize(1, grid.inv.numHotseatIcons);
 
             lp.height = ViewGroup.LayoutParams.MATCH_PARENT;
-            if (insets.left > insets.right) {
+            if (grid.isSeascape()) {
                 lp.gravity = Gravity.LEFT;
                 lp.width = grid.hotseatBarSizePx + insets.left + grid.hotseatBarSidePaddingPx;
                 getLayout().setPadding(
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
index a5ca3ee..957a5e5 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/IconCache.java
@@ -118,8 +118,7 @@
         mIconDpi = inv.fillResIconDpi;
         mIconDb = new IconDB(context, inv.iconBitmapSize);
 
-        mIconProvider = Utilities.getOverrideObject(
-                IconProvider.class, context, R.string.icon_provider_class);
+        mIconProvider = IconProvider.newInstance(context);
         mWorkerHandler = new Handler(LauncherModel.getWorkerLooper());
 
         mLowResOptions = new BitmapFactory.Options();
@@ -254,7 +253,7 @@
         // Remove all active icon update tasks.
         mWorkerHandler.removeCallbacksAndMessages(ICON_UPDATE_TOKEN);
 
-        mIconProvider.updateSystemStateString();
+        mIconProvider.updateSystemStateString(mContext);
         for (UserHandle user : mUserManager.getUserProfiles()) {
             // Query for the set of apps
             final List<LauncherActivityInfo> apps = mLauncherApps.getActivityList(null, user);
diff --git a/src/com/android/launcher3/IconProvider.java b/src/com/android/launcher3/IconProvider.java
index 4dee2b5..b469a8f 100644
--- a/src/com/android/launcher3/IconProvider.java
+++ b/src/com/android/launcher3/IconProvider.java
@@ -1,5 +1,6 @@
 package com.android.launcher3;
 
+import android.content.Context;
 import android.content.pm.LauncherActivityInfo;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
@@ -8,17 +9,26 @@
 
 public class IconProvider {
 
-    private static final boolean DBG = false;
-    private static final String TAG = "IconProvider";
-
     protected String mSystemState;
 
-    public IconProvider() {
-        updateSystemStateString();
+    public static IconProvider newInstance(Context context) {
+        IconProvider provider = Utilities.getOverrideObject(
+                IconProvider.class, context, R.string.icon_provider_class);
+        provider.updateSystemStateString(context);
+        return provider;
     }
 
-    public void updateSystemStateString() {
-        mSystemState = Locale.getDefault().toString() + "," + Build.VERSION.SDK_INT;
+    public IconProvider() { }
+
+    public void updateSystemStateString(Context context) {
+        final String locale;
+        if (Utilities.ATLEAST_NOUGAT) {
+            locale = context.getResources().getConfiguration().getLocales().toLanguageTags();
+        } else {
+            locale = Locale.getDefault().toString();
+        }
+
+        mSystemState = locale + "," + Build.VERSION.SDK_INT;
     }
 
     public String getIconSystemState(String packageName) {
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 5db0a3b..a91907d 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -214,6 +214,8 @@
     private static final int NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS = 5;
     @Thunk static final int NEW_APPS_ANIMATION_DELAY = 500;
 
+    private LauncherAppTransitionManager mAppTransitionManager;
+
     @Thunk Workspace mWorkspace;
     private View mLauncherView;
     @Thunk DragLayer mDragLayer;
@@ -403,8 +405,11 @@
         getSystemUiController().updateUiState(SystemUiController.UI_STATE_BASE_WINDOW,
                 Themes.getAttrBoolean(this, R.attr.isWorkspaceDarkText));
 
+        mAppTransitionManager = Utilities.getOverrideObject(LauncherAppTransitionManager.class,
+                        this, R.string.app_transition_manager_class);
+
         if (!isInMultiWindowModeCompat()) {
-            UiFactory.registerRemoteAnimations(this);
+            mAppTransitionManager.registerRemoteAnimations();
         }
 
         if (mLauncherCallbacks != null) {
@@ -1918,38 +1923,11 @@
         }
     }
 
-    public Bundle getDefaultActivityLaunchOptions(View v) {
-        if (Utilities.ATLEAST_MARSHMALLOW) {
-            int left = 0, top = 0;
-            int width = v.getMeasuredWidth(), height = v.getMeasuredHeight();
-            if (v instanceof BubbleTextView) {
-                // Launch from center of icon, not entire view
-                Drawable icon = ((BubbleTextView) v).getIcon();
-                if (icon != null) {
-                    Rect bounds = icon.getBounds();
-                    left = (width - bounds.width()) / 2;
-                    top = v.getPaddingTop();
-                    width = bounds.width();
-                    height = bounds.height();
-                }
-            }
-            return ActivityOptions.makeClipRevealAnimation(v, left, top, width, height)
-                    .toBundle();
-        } else if (Utilities.ATLEAST_LOLLIPOP_MR1) {
-            // On L devices, we use the device default slide-up transition.
-            // On L MR1 devices, we use a custom version of the slide-up transition which
-            // doesn't have the delay present in the device default.
-            return ActivityOptions.makeCustomAnimation(
-                    this, R.anim.task_open_enter, R.anim.no_anim).toBundle();
-        }
-        return null;
-    }
-
     @TargetApi(Build.VERSION_CODES.M)
     public Bundle getActivityLaunchOptions(View v, boolean useDefaultLaunchOptions) {
         return useDefaultLaunchOptions
-                ? getDefaultActivityLaunchOptions(v)
-                : UiFactory.getActivityLaunchOptions(this, v);
+                ? mAppTransitionManager.getDefaultActivityLaunchOptions(this, v)
+                : mAppTransitionManager.getActivityLaunchOptions(this, v);
     }
 
     public Rect getViewBounds(View v) {
diff --git a/src/com/android/launcher3/LauncherAppTransitionManager.java b/src/com/android/launcher3/LauncherAppTransitionManager.java
new file mode 100644
index 0000000..9d68dc9
--- /dev/null
+++ b/src/com/android/launcher3/LauncherAppTransitionManager.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+
+import android.app.ActivityOptions;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.view.View;
+
+/**
+ * Manages the opening and closing app transitions from Launcher.
+ */
+public class LauncherAppTransitionManager {
+
+    public Bundle getDefaultActivityLaunchOptions(Launcher launcher, View v) {
+        if (Utilities.ATLEAST_MARSHMALLOW) {
+            int left = 0, top = 0;
+            int width = v.getMeasuredWidth(), height = v.getMeasuredHeight();
+            if (v instanceof BubbleTextView) {
+                // Launch from center of icon, not entire view
+                Drawable icon = ((BubbleTextView) v).getIcon();
+                if (icon != null) {
+                    Rect bounds = icon.getBounds();
+                    left = (width - bounds.width()) / 2;
+                    top = v.getPaddingTop();
+                    width = bounds.width();
+                    height = bounds.height();
+                }
+            }
+            return ActivityOptions.makeClipRevealAnimation(v, left, top, width, height)
+                    .toBundle();
+        } else if (Utilities.ATLEAST_LOLLIPOP_MR1) {
+            // On L devices, we use the device default slide-up transition.
+            // On L MR1 devices, we use a custom version of the slide-up transition which
+            // doesn't have the delay present in the device default.
+            return ActivityOptions.makeCustomAnimation(launcher, R.anim.task_open_enter,
+                    R.anim.no_anim).toBundle();
+        }
+        return null;
+    }
+
+    public Bundle getActivityLaunchOptions(Launcher launcher, View v) {
+        return getDefaultActivityLaunchOptions(launcher, v);
+    }
+
+    public void registerRemoteAnimations() {
+    }
+}
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 2e544ec..dc3de18 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -55,13 +55,11 @@
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.keyboard.FocusedItemDecorator;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
-import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.ComponentKeyMapper;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.views.BottomUserEducationView;
 
-import java.util.HashMap;
 import java.util.List;
 import java.util.Set;
 
@@ -76,6 +74,7 @@
     private final ClickShadowView mTouchFeedbackView;
     private final ItemInfoMatcher mPersonalMatcher = ItemInfoMatcher.ofUser(Process.myUserHandle());
     private final ItemInfoMatcher mWorkMatcher = ItemInfoMatcher.not(mPersonalMatcher);
+    private final AllAppsStore mAllAppsStore = new AllAppsStore();
 
     private SearchUiManager mSearchUiManager;
     private View mSearchContainer;
@@ -92,8 +91,6 @@
     private boolean mHasPredictions = false;
     private boolean mSearchModeWhileUsingTabs = false;
 
-    private final HashMap<ComponentKey, AppInfo> mComponentToAppMap = new HashMap<>();
-
     public AllAppsContainerView(Context context) {
         this(context, null);
     }
@@ -132,6 +129,10 @@
         // TODO: Reimplement once fast scroller is fixed.
     }
 
+    public AllAppsStore getAppsStore() {
+        return mAllAppsStore;
+    }
+
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
@@ -149,64 +150,29 @@
     public void setApps(List<AppInfo> apps) {
         boolean hasWorkProfileApp = hasWorkProfileApp(apps);
         rebindAdapters(hasWorkProfileApp);
-        mComponentToAppMap.clear();
-        addOrUpdateApps(apps);
+        mAllAppsStore.setApps(apps);
     }
 
     /**
      * Adds or updates existing apps in the list
      */
     public void addOrUpdateApps(List<AppInfo> apps) {
-        for (AppInfo app : apps) {
-            mComponentToAppMap.put(app.toComponentKey(), app);
-        }
-        onAppsUpdated();
-        mSearchUiManager.refreshSearchResult();
-        mHeader.onAppsUpdated();
+        mAllAppsStore.addOrUpdateApps(apps);
     }
 
     /**
      * Removes some apps from the list.
      */
     public void removeApps(List<AppInfo> apps) {
-        for (AppInfo app : apps) {
-            mComponentToAppMap.remove(app.toComponentKey());
-        }
-        onAppsUpdated();
-        mSearchUiManager.refreshSearchResult();
-    }
-
-    private void onAppsUpdated() {
-        for (int i = 0; i < getNumOfAdapters(); i++) {
-            mAH[i].appsList.onAppsUpdated();
-        }
-    }
-
-    private int getNumOfAdapters() {
-        return mUsingTabs ? mAH.length : 1;
+        mAllAppsStore.removeApps(apps);
     }
 
     public void updatePromiseAppProgress(PromiseAppInfo app) {
-        for (int i = 0; i < mAH.length; i++) {
-            updatePromiseAppProgress(app, mAH[i].recyclerView);
-        }
-        if (isHeaderVisible()) {
-            updatePromiseAppProgress(app, mHeader.getPredictionRow());
-        }
-    }
-
-    private void updatePromiseAppProgress(PromiseAppInfo app, ViewGroup parent) {
-        if (parent == null) {
-            return;
-        }
-        int childCount = parent.getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            View child = parent.getChildAt(i);
-            if (child instanceof BubbleTextView && child.getTag() == app) {
-                BubbleTextView bubbleTextView = (BubbleTextView) child;
-                bubbleTextView.applyProgressLevel(app.level);
+        mAllAppsStore.updateAllIcons((child) -> {
+            if (child.getTag() == app) {
+                child.applyProgressLevel(app.level);
             }
-        }
+        });
     }
 
     /**
@@ -358,34 +324,15 @@
     }
 
     public void updateIconBadges(Set<PackageUserKey> updatedBadges) {
-        final PackageUserKey packageUserKey = new PackageUserKey(null, null);
-        for (int j = 0; j < mAH.length; j++) {
-            updateIconBadges(updatedBadges, packageUserKey, mAH[j].recyclerView);
-        }
-        if (mHeader != null) {
-            updateIconBadges(updatedBadges, packageUserKey, mHeader.getPredictionRow());
-        }
-    }
-
-    private void updateIconBadges(Set<PackageUserKey> updatedBadges, PackageUserKey packageUserKey,
-            ViewGroup parent) {
-        if (parent == null) {
-            return;
-        }
-        final int n = parent.getChildCount();
-        for (int i = 0; i < n; i++) {
-            View child = parent.getChildAt(i);
-            if (child instanceof PredictionRowView) {
-                updateIconBadges(updatedBadges, packageUserKey, (PredictionRowView) child);
+        PackageUserKey tempKey = new PackageUserKey(null, null);
+        mAllAppsStore.updateAllIcons((child) -> {
+            if (child.getTag() instanceof ItemInfo) {
+                ItemInfo info = (ItemInfo) child.getTag();
+                if (tempKey.updateFromItemInfo(info) && updatedBadges.contains(tempKey)) {
+                    child.applyBadgeState(info, true /* animate */);
+                }
             }
-            if (!(child instanceof BubbleTextView) || !(child.getTag() instanceof ItemInfo)) {
-                continue;
-            }
-            ItemInfo info = (ItemInfo) child.getTag();
-            if (packageUserKey.updateFromItemInfo(info) && updatedBadges.contains(packageUserKey)) {
-                ((BubbleTextView) child).applyBadgeState(info, true /* animate */);
-            }
-        }
+        });
     }
 
     public SpringAnimationHandler getSpringAnimationHandler() {
@@ -403,6 +350,9 @@
         replaceRVContainer(showTabs);
         mUsingTabs = showTabs;
 
+        mAllAppsStore.unregisterIconContainer(mAH[AdapterHolder.MAIN].recyclerView);
+        mAllAppsStore.unregisterIconContainer(mAH[AdapterHolder.WORK].recyclerView);
+
         if (mUsingTabs) {
             mAH[AdapterHolder.MAIN].setup(mViewPager.getChildAt(0), mPersonalMatcher);
             mAH[AdapterHolder.WORK].setup(mViewPager.getChildAt(1), mWorkMatcher);
@@ -419,6 +369,9 @@
             }
         }
 
+        mAllAppsStore.registerIconContainer(mAH[AdapterHolder.MAIN].recyclerView);
+        mAllAppsStore.registerIconContainer(mAH[AdapterHolder.WORK].recyclerView);
+
         applyTouchDelegate();
     }
 
@@ -492,9 +445,6 @@
     }
 
     public void setPredictedApps(List<ComponentKeyMapper<AppInfo>> apps) {
-        if (isHeaderVisible()) {
-            mHeader.getPredictionRow().setPredictedApps(apps);
-        }
         mAH[AdapterHolder.MAIN].appsList.setPredictedApps(apps);
         boolean hasPredictions = !apps.isEmpty();
         if (mHasPredictions != hasPredictions) {
@@ -506,7 +456,7 @@
     }
 
     public AppInfo findApp(ComponentKeyMapper<AppInfo> mapper) {
-        return mapper.getItem(mComponentToAppMap);
+        return mAllAppsStore.getApp(mapper);
     }
 
     public AlphabeticalAppsList getApps() {
@@ -526,9 +476,9 @@
             return;
         }
         mHeader.setVisibility(View.VISIBLE);
-        mHeader.setup(mAH, mComponentToAppMap, mNumPredictedAppsPerRow);
+        mHeader.setup(mAH, mAH[AllAppsContainerView.AdapterHolder.WORK].recyclerView == null);
 
-        int padding = mHeader.getPredictionRow().getExpectedHeight();
+        int padding = mHeader.getMaxTranslation();
         if (mHasPredictions && !mUsingTabs) {
             padding += mHeader.getPaddingTop() + mHeader.getPaddingBottom();
         }
@@ -582,14 +532,6 @@
         }
     }
 
-    public List<AppInfo> getPredictedApps() {
-        if (isHeaderVisible()) {
-            return mHeader.getPredictionRow().getPredictedApps();
-        } else {
-            return mAH[AdapterHolder.MAIN].appsList.getPredictedApps();
-        }
-    }
-
     public boolean isHeaderVisible() {
         return mHeader != null && mHeader.getVisibility() == View.VISIBLE;
     }
@@ -604,7 +546,7 @@
         public static final int MAIN = 0;
         public static final int WORK = 1;
 
-        final AllAppsGridAdapter adapter;
+        public final AllAppsGridAdapter adapter;
         final LinearLayoutManager layoutManager;
         final SpringAnimationHandler animationHandler;
         final AlphabeticalAppsList appsList;
@@ -614,7 +556,7 @@
         boolean verticalFadingEdge;
 
         AdapterHolder(boolean isWork) {
-            appsList = new AlphabeticalAppsList(mLauncher, mComponentToAppMap, isWork);
+            appsList = new AlphabeticalAppsList(mLauncher, mAllAppsStore, isWork);
             adapter = new AllAppsGridAdapter(mLauncher, appsList, mLauncher,
                     AllAppsContainerView.this, true);
             appsList.setAdapter(adapter);
@@ -649,11 +591,6 @@
                         ? paddingTopForTabs : padding.top;
                 recyclerView.setPadding(padding.left, paddingTop, padding.right, padding.bottom);
             }
-            if (isHeaderVisible()) {
-                PredictionRowView prv = mHeader.getPredictionRow();
-                prv.setPadding(padding.left, prv.getPaddingTop() , padding.right,
-                        prv.getPaddingBottom());
-            }
         }
 
         void applyNumsPerRow() {
@@ -663,10 +600,6 @@
                 }
                 adapter.setNumAppsPerRow(mNumAppsPerRow);
                 appsList.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow);
-                if (isHeaderVisible()) {
-                    mHeader.getPredictionRow()
-                            .setNumAppsPerRow(mNumPredictedAppsPerRow);
-                }
             }
         }
 
diff --git a/src/com/android/launcher3/allapps/AllAppsStore.java b/src/com/android/launcher3/allapps/AllAppsStore.java
new file mode 100644
index 0000000..17f1c89
--- /dev/null
+++ b/src/com/android/launcher3/allapps/AllAppsStore.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.allapps;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.ComponentKeyMapper;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * A utility class to maintain the collection of all apps.
+ */
+public class AllAppsStore {
+
+    private final HashMap<ComponentKey, AppInfo> mComponentToAppMap = new HashMap<>();
+    private final List<OnUpdateListener> mUpdateListeners = new ArrayList<>();
+    private final ArrayList<ViewGroup> mIconContainers = new ArrayList<>();
+
+    public Collection<AppInfo> getApps() {
+        return mComponentToAppMap.values();
+    }
+
+    /**
+     * Sets the current set of apps.
+     */
+    public void setApps(List<AppInfo> apps) {
+        mComponentToAppMap.clear();
+        addOrUpdateApps(apps);
+    }
+
+    public AppInfo getApp(ComponentKey key) {
+        return mComponentToAppMap.get(key);
+    }
+
+    public AppInfo getApp(ComponentKeyMapper<AppInfo> mapper) {
+        return mapper.getItem(mComponentToAppMap);
+    }
+
+    /**
+     * Adds or updates existing apps in the list
+     */
+    public void addOrUpdateApps(List<AppInfo> apps) {
+        for (AppInfo app : apps) {
+            mComponentToAppMap.put(app.toComponentKey(), app);
+        }
+        notifyUpdate();
+    }
+
+    /**
+     * Removes some apps from the list.
+     */
+    public void removeApps(List<AppInfo> apps) {
+        for (AppInfo app : apps) {
+            mComponentToAppMap.remove(app.toComponentKey());
+        }
+        notifyUpdate();
+    }
+
+
+    private void notifyUpdate() {
+        int count = mUpdateListeners.size();
+        for (int i = 0; i < count; i++) {
+            mUpdateListeners.get(i).onAppsUpdated();
+        }
+    }
+
+    public void addUpdateListener(OnUpdateListener listener) {
+        mUpdateListeners.add(listener);
+    }
+
+    public void removeUpdateListener(OnUpdateListener listener) {
+        mUpdateListeners.remove(listener);
+    }
+
+    public void registerIconContainer(ViewGroup container) {
+        if (container != null) {
+            mIconContainers.add(container);
+        }
+    }
+
+    public void unregisterIconContainer(ViewGroup container) {
+        mIconContainers.remove(container);
+    }
+
+    public void updateAllIcons(IconAction action) {
+        for (int i = mIconContainers.size() - 1; i >= 0; i--) {
+            ViewGroup parent = mIconContainers.get(i);
+            int childCount = parent.getChildCount();
+
+            for (int j = 0; j < childCount; j++) {
+                View child = parent.getChildAt(j);
+                if (child instanceof BubbleTextView) {
+                    action.apply((BubbleTextView) child);
+                }
+            }
+        }
+    }
+
+    public interface OnUpdateListener {
+        void onAppsUpdated();
+    }
+
+    public interface IconAction {
+        void apply(BubbleTextView icon);
+    }
+}
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index 76828de..29b32b0 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -44,7 +44,7 @@
 /**
  * The alphabetically sorted list of applications.
  */
-public class AlphabeticalAppsList {
+public class AlphabeticalAppsList implements AllAppsStore.OnUpdateListener {
 
     public static final String TAG = "AlphabeticalAppsList";
     private static final boolean DEBUG = false;
@@ -153,7 +153,7 @@
 
     // The set of apps from the system not including predictions
     private final List<AppInfo> mApps = new ArrayList<>();
-    private final HashMap<ComponentKey, AppInfo> mComponentToAppMap;
+    private final AllAppsStore mAllAppsStore;
 
     // The set of filtered apps with the current filter
     private final List<AppInfo> mFilteredApps = new ArrayList<>();
@@ -179,16 +179,13 @@
     private int mNumAppRowsInAdapter;
     private ItemInfoMatcher mItemFilter;
 
-    public AlphabeticalAppsList(
-            Context context,
-            HashMap<ComponentKey,
-            AppInfo> componentToAppMap,
-            boolean isWork) {
-        mComponentToAppMap = componentToAppMap;
+    public AlphabeticalAppsList(Context context, AllAppsStore appsStore, boolean isWork) {
+        mAllAppsStore = appsStore;
         mLauncher = Launcher.getLauncher(context);
         mIndexer = new AlphabeticIndexCompat(context);
         mAppNameComparator = new AppInfoComparator(context);
         mIsWork = isWork;
+        mAllAppsStore.addUpdateListener(this);
     }
 
     public void updateItemFilter(ItemInfoMatcher itemFilter) {
@@ -283,14 +280,14 @@
     }
 
     private List<AppInfo> processPredictedAppComponents(List<ComponentKeyMapper<AppInfo>> components) {
-        if (mComponentToAppMap.isEmpty()) {
+        if (mAllAppsStore.getApps().isEmpty()) {
             // Apps have not been bound yet.
             return Collections.emptyList();
         }
 
         List<AppInfo> predictedApps = new ArrayList<>();
         for (ComponentKeyMapper<AppInfo> mapper : components) {
-            AppInfo info = mapper.getItem(mComponentToAppMap);
+            AppInfo info = mAllAppsStore.getApp(mapper);
             if (info != null) {
                 predictedApps.add(info);
             } else {
@@ -359,11 +356,12 @@
     /**
      * Updates internals when the set of apps are updated.
      */
-    void onAppsUpdated() {
+    @Override
+    public void onAppsUpdated() {
         // Sort the list of apps
         mApps.clear();
 
-        for (AppInfo app : mComponentToAppMap.values()) {
+        for (AppInfo app : mAllAppsStore.getApps()) {
             if (mItemFilter == null || mItemFilter.matches(app, null) || hasFilter()) {
                 mApps.add(app);
             }
@@ -580,7 +578,7 @@
         }
         ArrayList<AppInfo> result = new ArrayList<>();
         for (ComponentKey key : mSearchResults) {
-            AppInfo match = mComponentToAppMap.get(key);
+            AppInfo match = mAllAppsStore.getApp(key);
             if (match != null) {
                 result.add(match);
             }
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderView.java b/src/com/android/launcher3/allapps/FloatingHeaderView.java
index 2391768..d8a9f63 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderView.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderView.java
@@ -15,7 +15,6 @@
  */
 package com.android.launcher3.allapps;
 
-
 import android.animation.ValueAnimator;
 import android.content.Context;
 import android.graphics.Point;
@@ -27,18 +26,14 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.LinearLayout;
 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
+public class FloatingHeaderView extends LinearLayout implements
         ValueAnimator.AnimatorUpdateListener {
 
-
     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();
@@ -63,19 +58,18 @@
         }
     };
 
-    private PredictionRowView mPredictionRow;
     private ViewGroup mTabLayout;
     private AllAppsRecyclerView mMainRV;
     private AllAppsRecyclerView mWorkRV;
     private AllAppsRecyclerView mCurrentRV;
     private ViewGroup mParent;
-    private boolean mTabsHidden;
     private boolean mHeaderCollapsed;
-    private int mMaxTranslation;
     private int mSnappedScrolledY;
     private int mTranslationY;
     private boolean mForwardToRecyclerView;
 
+    protected int mMaxTranslation;
+
     public FloatingHeaderView(@NonNull Context context) {
         this(context, null);
     }
@@ -88,17 +82,10 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
         mTabLayout = findViewById(R.id.tabs);
-        mPredictionRow = findViewById(R.id.header_content);
     }
 
-    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.setup(mAH[AllAppsContainerView.AdapterHolder.MAIN].adapter,
-                componentToAppMap, numPredictedAppsPerRow);
-        mPredictionRow.setShowDivider(mTabsHidden);
-        mMaxTranslation = mPredictionRow.getExpectedHeight();
+    public void setup(AllAppsContainerView.AdapterHolder[] mAH, boolean tabsHidden) {
+        mTabLayout.setVisibility(tabsHidden ? View.GONE : View.VISIBLE);
         mMainRV = setupRV(mMainRV, mAH[AllAppsContainerView.AdapterHolder.MAIN].recyclerView);
         mWorkRV = setupRV(mWorkRV, mAH[AllAppsContainerView.AdapterHolder.WORK].recyclerView);
         mParent = (ViewGroup) mMainRV.getParent();
@@ -117,12 +104,12 @@
         mCurrentRV = active ? mMainRV : mWorkRV;
     }
 
-    public PredictionRowView getPredictionRow() {
-        return mPredictionRow;
+    public int getMaxTranslation() {
+        return mMaxTranslation;
     }
 
     private boolean canSnapAt(int currentScrollY) {
-        return Math.abs(currentScrollY) <= mPredictionRow.getHeight();
+        return Math.abs(currentScrollY) <= mMaxTranslation;
     }
 
     private void moved(final int currentScrollY) {
@@ -149,16 +136,12 @@
         }
     }
 
-    private void apply() {
+    protected void applyScroll(int uncappedY, int currentY) { }
+
+    protected void apply() {
         int uncappedTranslationY = mTranslationY;
         mTranslationY = Math.max(mTranslationY, -mMaxTranslation);
-        if (mTranslationY != uncappedTranslationY) {
-            // we hide it completely if already capped (for opening search anim)
-            mPredictionRow.setVisibility(View.INVISIBLE);
-        } else {
-            mPredictionRow.setVisibility(View.VISIBLE);
-            mPredictionRow.setTranslationY(uncappedTranslationY);
-        }
+        applyScroll(uncappedTranslationY, mTranslationY);
         mTabLayout.setTranslationY(mTranslationY);
         mClip.top = mMaxTranslation + mTranslationY;
         // clipping on a draw might cause additional redraw
@@ -218,10 +201,6 @@
         p.x = getLeft() - mCurrentRV.getLeft() - mParent.getLeft();
         p.y = getTop() - mCurrentRV.getTop() - mParent.getTop();
     }
-
-    public void onAppsUpdated() {
-        mPredictionRow.onAppsUpdated();
-    }
 }
 
 
diff --git a/src/com/android/launcher3/allapps/PredictionRowView.java b/src/com/android/launcher3/allapps/PredictionRowView.java
deleted file mode 100644
index 267ef3c..0000000
--- a/src/com/android/launcher3/allapps/PredictionRowView.java
+++ /dev/null
@@ -1,245 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.allapps;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.View;
-import android.widget.LinearLayout;
-
-import com.android.launcher3.AppInfo;
-import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.R;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.logging.UserEventDispatcher;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
-import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.ComponentKeyMapper;
-import com.android.launcher3.util.Themes;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-
-public class PredictionRowView extends LinearLayout implements
-        UserEventDispatcher.LogContainerProvider {
-
-    private static final String TAG = "PredictionRowView";
-
-    private HashMap<ComponentKey, AppInfo> mComponentToAppMap;
-    private int mNumPredictedAppsPerRow;
-    // The set of predicted app component names
-    private final List<ComponentKeyMapper<AppInfo>> mPredictedAppComponents = new ArrayList<>();
-    // The set of predicted apps resolved from the component names and the current set of apps
-    private final ArrayList<AppInfo> mPredictedApps = new ArrayList<>();
-    private final Paint mPaint;
-    // This adapter is only used to create an identical item w/ same behavior as in the all apps RV
-    private AllAppsGridAdapter mAdapter;
-    private boolean mShowDivider;
-
-    public PredictionRowView(@NonNull Context context) {
-        this(context, null);
-    }
-
-    public PredictionRowView(@NonNull Context context, @Nullable AttributeSet attrs) {
-        super(context, attrs);
-        setOrientation(LinearLayout.HORIZONTAL);
-        setWillNotDraw(false);
-        mPaint = new Paint();
-        mPaint.setColor(Themes.getAttrColor(context, android.R.attr.colorControlHighlight));
-        mPaint.setStrokeWidth(getResources().getDimensionPixelSize(R.dimen.all_apps_divider_height));
-    }
-
-    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;
-    }
-
-    public void setShowDivider(boolean showDivider) {
-        mShowDivider = showDivider;
-        int paddingBottom = showDivider ? getResources()
-                .getDimensionPixelSize(R.dimen.all_apps_prediction_row_divider_height) : 0;
-        setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), paddingBottom);
-    }
-
-    /**
-     * Sets the number of apps per row.
-     */
-    public void setNumAppsPerRow(int numPredictedAppsPerRow) {
-        if (mNumPredictedAppsPerRow != numPredictedAppsPerRow) {
-            mNumPredictedAppsPerRow = numPredictedAppsPerRow;
-            onPredictionsUpdated();
-        }
-    }
-
-    /**
-     * Returns the predicted apps.
-     */
-    public List<AppInfo> getPredictedApps() {
-        return mPredictedApps;
-    }
-
-    /**
-     * Sets the current set of predicted apps.
-     *
-     * This can be called before we get the full set of applications, we should merge the results
-     * only in onPredictionsUpdated() which is idempotent.
-     *
-     * If the number of predicted apps is the same as the previous list of predicted apps,
-     * we can optimize by swapping them in place.
-     */
-    public void setPredictedApps(List<ComponentKeyMapper<AppInfo>> apps) {
-        mPredictedAppComponents.clear();
-        mPredictedAppComponents.addAll(apps);
-        mPredictedApps.clear();
-        mPredictedApps.addAll(processPredictedAppComponents(mPredictedAppComponents));
-        onPredictionsUpdated();
-    }
-
-    private void onPredictionsUpdated() {
-        int childCountBefore = getChildCount();
-        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 {
-                icon.setVisibility(View.INVISIBLE);
-            }
-        }
-
-        if (getChildCount() > 0 && childCountBefore == 0
-                || getChildCount() == 0 && childCountBefore > 0) {
-            // setting up header to adjust the height
-            // only necessary if childcount switches from/to 0
-            Launcher.getLauncher(getContext()).getAppsView().setupHeader();
-        }
-    }
-
-    /**
-     * Refreshes the app icons in the row view, while preserving the same set of predictions.
-     */
-    public void onAppsUpdated() {
-        for (int i = 0; i < getChildCount(); i++) {
-            View child = getChildAt(i);
-            if (!(child instanceof BubbleTextView)) {
-                continue;
-            }
-            if (i >= mPredictedApps.size()) {
-                break;
-            }
-            BubbleTextView icon = (BubbleTextView) getChildAt(i);
-            icon.reset();
-            icon.applyFromApplicationInfo(mPredictedApps.get(i));
-        }
-    }
-
-    private List<AppInfo> processPredictedAppComponents(
-            List<ComponentKeyMapper<AppInfo>> components) {
-        if (mComponentToAppMap.isEmpty()) {
-            // Apps have not been bound yet.
-            return Collections.emptyList();
-        }
-
-        List<AppInfo> predictedApps = new ArrayList<>();
-        for (ComponentKeyMapper<AppInfo> mapper : components) {
-            AppInfo info = mapper.getItem(mComponentToAppMap);
-            if (info != null) {
-                predictedApps.add(info);
-            } else {
-                if (FeatureFlags.IS_DOGFOOD_BUILD) {
-                    Log.e(TAG, "Predicted app not found: " + mapper);
-                }
-            }
-            // Stop at the number of predicted apps
-            if (predictedApps.size() == mNumPredictedAppsPerRow) {
-                break;
-            }
-        }
-        return predictedApps;
-    }
-
-    @Override
-    protected void onDraw(Canvas canvas) {
-        super.onDraw(canvas);
-
-        if (mShowDivider) {
-            int side = getResources().getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
-            int y = getHeight() - (getPaddingBottom() / 2);
-            int x1 = getPaddingLeft() + side;
-            int x2 = getWidth() - getPaddingRight() - side;
-            canvas.drawLine(x1, y, x2, y, mPaint);
-        }
-    }
-
-    @Override
-    public void fillInLogContainerData(View v, ItemInfo info, LauncherLogProto.Target target,
-            LauncherLogProto.Target targetParent) {
-        for (int i = 0; i < mPredictedApps.size(); i++) {
-            AppInfo appInfo = mPredictedApps.get(i);
-            if (appInfo == info) {
-                targetParent.containerType = LauncherLogProto.ContainerType.PREDICTION;
-                target.predictedRank = i;
-                break;
-            }
-        }
-    }
-}
diff --git a/src/com/android/launcher3/allapps/SearchUiManager.java b/src/com/android/launcher3/allapps/SearchUiManager.java
index f562b6a..bb17ed5 100644
--- a/src/com/android/launcher3/allapps/SearchUiManager.java
+++ b/src/com/android/launcher3/allapps/SearchUiManager.java
@@ -35,12 +35,6 @@
     @NonNull SpringAnimation getSpringForFling();
 
     /**
-     * Notifies the search manager that the apps-list has changed and the search UI should be
-     * updated accordingly.
-     */
-    void refreshSearchResult();
-
-    /**
      * Notifies the search manager to close any active search session.
      */
     void reset();
diff --git a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
index 6f07eeb..a56c8b8 100644
--- a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
+++ b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
@@ -21,7 +21,6 @@
 import android.support.animation.SpringAnimation;
 import android.support.animation.SpringForce;
 import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
 import android.text.Selection;
 import android.text.Spannable;
 import android.text.SpannableString;
@@ -37,6 +36,7 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.allapps.AllAppsContainerView;
+import com.android.launcher3.allapps.AllAppsStore;
 import com.android.launcher3.allapps.AlphabeticalAppsList;
 import com.android.launcher3.allapps.SearchUiManager;
 import com.android.launcher3.graphics.TintedDrawableSpan;
@@ -48,7 +48,8 @@
  * Layout to contain the All-apps search UI.
  */
 public class AppsSearchContainerLayout extends FrameLayout
-        implements SearchUiManager, AllAppsSearchBarController.Callbacks {
+        implements SearchUiManager, AllAppsSearchBarController.Callbacks,
+        AllAppsStore.OnUpdateListener {
 
     private final Launcher mLauncher;
     private final int mMinHeight;
@@ -111,9 +112,22 @@
     }
 
     @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        mLauncher.getAppsView().getAppsStore().addUpdateListener(this);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        mLauncher.getAppsView().getAppsStore().removeUpdateListener(this);
+    }
+
+    @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        if (!mLauncher.getDeviceProfile().isVerticalBarLayout()) {
-            getLayoutParams().height = mLauncher.getDragLayer().getInsets().top + mMinHeight;
+        DeviceProfile dp = mLauncher.getDeviceProfile();
+        if (!dp.isVerticalBarLayout()) {
+            getLayoutParams().height = dp.getInsets().top + mMinHeight;
         }
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
     }
@@ -134,7 +148,7 @@
     }
 
     @Override
-    public void refreshSearchResult() {
+    public void onAppsUpdated() {
         mSearchBarController.refreshSearchResult();
     }
 
@@ -198,7 +212,7 @@
                     int oldLeft, int oldTop, int oldRight, int oldBottom) {
                 DeviceProfile dp = mLauncher.getDeviceProfile();
                 if (!dp.isVerticalBarLayout()) {
-                    Rect insets = mLauncher.getDragLayer().getInsets();
+                    Rect insets = dp.getInsets();
                     int hotseatBottom = bottom - dp.hotseatBarBottomPaddingPx - insets.bottom;
                     int searchTopMargin = insets.top + (mMinHeight - mSearchBoxHeight)
                             + ((MarginLayoutParams) getLayoutParams()).bottomMargin;
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index 677694f..a32f6b1 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -52,7 +52,6 @@
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.keyboard.ViewGroupFocusHelper;
 import com.android.launcher3.uioverrides.UiFactory;
-import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.util.TouchController;
 
diff --git a/src/com/android/launcher3/graphics/GradientView.java b/src/com/android/launcher3/graphics/GradientView.java
index bacb063..6253e18 100644
--- a/src/com/android/launcher3/graphics/GradientView.java
+++ b/src/com/android/launcher3/graphics/GradientView.java
@@ -75,7 +75,7 @@
         this.mMaskHeight = Utilities.pxFromDp(ALPHA_MASK_HEIGHT_DP, dm);
         this.mMaskWidth = Utilities.pxFromDp(ALPHA_MASK_WIDTH_DP, dm);
         Launcher launcher = Launcher.getLauncher(context);
-        this.mAlphaStart = launcher.getDeviceProfile().isVerticalBarLayout() ? 0 : 100;
+        this.mAlphaStart = 0;
         this.mScrimColor = Themes.getAttrColor(context, R.attr.allAppsScrimColor);
         this.mWallpaperColorInfo = WallpaperColorInfo.getInstance(launcher);
         mAlphaColors = getResources().getInteger(R.integer.extracted_color_gradient_alpha);
diff --git a/src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java b/src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java
index f3b0d61..3e4bd31 100644
--- a/src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java
+++ b/src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java
@@ -22,6 +22,7 @@
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.ViewConfiguration;
+import android.view.accessibility.AccessibilityManager;
 import android.widget.FrameLayout;
 
 import com.android.launcher3.DeviceProfile;
@@ -55,6 +56,7 @@
 
     private final Handler mDelayedLineFadeHandler = new Handler(Looper.getMainLooper());
     private final Launcher mLauncher;
+    private final AccessibilityManager mAccessibilityManager;
 
     private boolean mShouldAutoHide = true;
 
@@ -136,6 +138,8 @@
         boolean darkText = WallpaperColorInfo.getInstance(context).supportsDarkText();
         mActiveAlpha = darkText ? BLACK_ALPHA : WHITE_ALPHA;
         mLinePaint.setColor(darkText ? Color.BLACK : Color.WHITE);
+        mAccessibilityManager = (AccessibilityManager)
+                getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
     }
 
     @Override
@@ -249,7 +253,7 @@
         FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
 
         if (mIsLandscapeUi) {
-            if (insets.left > insets.right) {
+            if (grid.isSeascape()) {
                 lp.leftMargin = grid.hotseatBarSidePaddingPx;
                 lp.rightMargin = insets.right;
                 lp.gravity =  Gravity.RIGHT | Gravity.BOTTOM;
@@ -274,7 +278,7 @@
 
             setBackgroundResource(0);
             setOnFocusChangeListener(null);
-            setOnClickListener(null);
+            setOnClickListener(mAccessibilityManager.isTouchExplorationEnabled() ? this : null);
         }
 
         setLayoutParams(lp);
diff --git a/src/com/android/launcher3/util/ComponentKeyMapper.java b/src/com/android/launcher3/util/ComponentKeyMapper.java
index 916176a..a7f0d76 100644
--- a/src/com/android/launcher3/util/ComponentKeyMapper.java
+++ b/src/com/android/launcher3/util/ComponentKeyMapper.java
@@ -18,8 +18,6 @@
 
 import android.support.annotation.Nullable;
 
-import java.util.ArrayList;
-import java.util.List;
 import java.util.Map;
 
 public class ComponentKeyMapper<T> {
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
index c857bf6..744125e 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
@@ -61,10 +61,4 @@
     }
 
     public static void resetOverview(Launcher launcher) { }
-
-    public static Bundle getActivityLaunchOptions(Launcher launcher, View v) {
-        return launcher.getDefaultActivityLaunchOptions(v);
-    }
-
-    public static void registerRemoteAnimations(Launcher launcher) { }
 }