Merge "Removing call to isButtonPressed as its only Lollipop and above" into ub-launcher3-burnaby
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 3af38f3..99a6ad9 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -20,7 +20,7 @@
 <manifest
     xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.launcher3">
-    <uses-sdk android:targetSdkVersion="21" android:minSdkVersion="16"/>
+    <uses-sdk android:targetSdkVersion="23" android:minSdkVersion="16"/>
 
     <permission
         android:name="com.android.launcher.permission.INSTALL_SHORTCUT"
diff --git a/WallpaperPicker/res/values-v19/styles.xml b/WallpaperPicker/res/values-v19/styles.xml
index 136cf01..15fb0ea 100644
--- a/WallpaperPicker/res/values-v19/styles.xml
+++ b/WallpaperPicker/res/values-v19/styles.xml
@@ -25,7 +25,7 @@
         <item name="android:windowTranslucentNavigation">true</item>
     </style>
 
-    <style name="Theme" parent="@android:style/Theme.DeviceDefault.Wallpaper.NoTitleBar">
+    <style name="Theme" parent="@style/BaseWallpaperTheme">
         <item name="android:windowTranslucentStatus">true</item>
         <item name="android:windowTranslucentNavigation">true</item>
     </style>
diff --git a/WallpaperPicker/res/values-v21/styles.xml b/WallpaperPicker/res/values-v21/styles.xml
index 582ab8f..70220ed 100644
--- a/WallpaperPicker/res/values-v21/styles.xml
+++ b/WallpaperPicker/res/values-v21/styles.xml
@@ -33,4 +33,11 @@
         <item name="android:background">?android:attr/selectableItemBackgroundBorderless</item>
     </style>
 
+    <style name="Theme" parent="@style/BaseWallpaperTheme">
+        <item name="android:windowTranslucentStatus">true</item>
+        <item name="android:windowTranslucentNavigation">true</item>
+        <item name="android:colorControlActivated">@color/launcher_accent_color</item>
+        <item name="android:colorAccent">@color/launcher_accent_color</item>
+        <item name="android:colorPrimary">@color/launcher_accent_color</item>
+    </style>
 </resources>
\ No newline at end of file
diff --git a/WallpaperPicker/res/values/colors.xml b/WallpaperPicker/res/values/colors.xml
index adae7cf..6ba32f0 100644
--- a/WallpaperPicker/res/values/colors.xml
+++ b/WallpaperPicker/res/values/colors.xml
@@ -19,4 +19,6 @@
 -->
 <resources>
     <color name="wallpaper_picker_translucent_gray">#66000000</color>
+
+    <color name="launcher_accent_color">#ff009688</color>
 </resources>
diff --git a/WallpaperPicker/res/values/styles.xml b/WallpaperPicker/res/values/styles.xml
index 74aeab9..d1c945a 100644
--- a/WallpaperPicker/res/values/styles.xml
+++ b/WallpaperPicker/res/values/styles.xml
@@ -35,9 +35,15 @@
         <item name="android:background">#88000000</item>
     </style>
 
-    <style name="Theme" parent="@android:style/Theme.DeviceDefault.Wallpaper.NoTitleBar">
+    <style name="BaseWallpaperTheme" parent="@android:style/Theme.DeviceDefault.Light.NoActionBar">
+        <item name="android:windowBackground">@android:color/transparent</item>
+        <item name="android:colorBackgroundCacheHint">@null</item>
+        <item name="android:windowShowWallpaper">true</item>
+        <item name="android:windowNoTitle">true</item>
     </style>
 
+    <style name="Theme" parent="@style/BaseWallpaperTheme"></style>
+
     <style name="ActionBarSetWallpaperStyle" parent="@android:style/Widget.DeviceDefault.ActionButton">
         <item name="android:textColor">#ffffffff</item>
         <item name="android:background">?android:attr/selectableItemBackground</item>
diff --git a/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java b/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java
index f2bb509..f2459dd 100644
--- a/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java
+++ b/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java
@@ -223,14 +223,12 @@
 
     @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
     protected boolean isActivityDestroyed() {
-        return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1)
-                && isDestroyed();
+        return Utilities.ATLEAST_JB_MR1 && isDestroyed();
     }
 
     @Thunk void addReusableBitmap(TileSource src) {
         synchronized (mReusableBitmaps) {
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
-                    && src instanceof BitmapRegionTileSource) {
+            if (Utilities.ATLEAST_KITKAT && src instanceof BitmapRegionTileSource) {
                 Bitmap preview = ((BitmapRegionTileSource) src).getBitmap();
                 if (preview != null && preview.isMutable()) {
                     mReusableBitmaps.add(preview);
diff --git a/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java b/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java
index 75d8745..5985850 100644
--- a/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java
+++ b/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java
@@ -977,10 +977,8 @@
 
         if (partner == null || !partner.hideDefaultWallpaper()) {
             // Add an entry for the default wallpaper (stored in system resources)
-            WallpaperTileInfo defaultWallpaperInfo =
-                    (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT)
-                    ? getPreKKDefaultWallpaperInfo()
-                    : getDefaultWallpaper();
+            WallpaperTileInfo defaultWallpaperInfo = Utilities.ATLEAST_KITKAT
+                    ? getDefaultWallpaper() : getPreKKDefaultWallpaperInfo();
             if (defaultWallpaperInfo != null) {
                 bundled.add(0, defaultWallpaperInfo);
             }
diff --git a/proguard.flags b/proguard.flags
index a8e2b60..e6c4c51 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -3,10 +3,10 @@
 }
 
 -keep class com.android.launcher3.BaseRecyclerViewFastScrollBar {
-  public void setWidth(int);
-  public int getWidth();
-  public void setTrackAlpha(int);
-  public int getTrackAlpha();
+  public void setThumbWidth(int);
+  public int getThumbWidth();
+  public void setTrackWidth(int);
+  public int getTrackWidth();
 }
 
 -keep class com.android.launcher3.BaseRecyclerViewFastScrollPopup {
diff --git a/res/layout/all_apps_search_market.xml b/res/layout/all_apps_search_market.xml
index 1282069..1ed5088 100644
--- a/res/layout/all_apps_search_market.xml
+++ b/res/layout/all_apps_search_market.xml
@@ -23,7 +23,7 @@
     android:paddingRight="16dp"
     android:fontFamily="sans-serif-medium"
     android:textSize="14sp"
-    android:textColor="#009688"
+    android:textColor="@color/launcher_accent_color"
     android:textAllCaps="true"
     android:focusable="false"
     android:background="@drawable/all_apps_search_market_bg" />
diff --git a/res/layout/overview_panel.xml b/res/layout/overview_panel.xml
index 4b7423e..1f02dce 100644
--- a/res/layout/overview_panel.xml
+++ b/res/layout/overview_panel.xml
@@ -32,6 +32,7 @@
         android:gravity="center_horizontal"
         android:text="@string/wallpaper_button_text"
         android:textAllCaps="true"
+        android:textColor="@android:color/white"
         android:textSize="12sp" />
 
     <TextView
@@ -45,6 +46,7 @@
         android:gravity="center_horizontal"
         android:text="@string/widget_button_text"
         android:textAllCaps="true"
+        android:textColor="@android:color/white"
         android:textSize="12sp" />
 
     <TextView
@@ -58,6 +60,7 @@
         android:gravity="center_horizontal"
         android:text="@string/settings_button_text"
         android:textAllCaps="true"
+        android:textColor="@android:color/white"
         android:textSize="12sp" />
 
 </LinearLayout>
\ No newline at end of file
diff --git a/res/layout/user_folder.xml b/res/layout/user_folder.xml
index 275840d..252ebf0 100644
--- a/res/layout/user_folder.xml
+++ b/res/layout/user_folder.xml
@@ -70,7 +70,6 @@
             android:textColor="#ff777777"
             android:textColorHighlight="#ffCCCCCC"
             android:textColorHint="#ff808080"
-            android:textCursorDrawable="@null"
             android:textSize="14sp" />
 
         <include
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 0add48c..6fe2ae1 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -39,7 +39,7 @@
     <color name="outline_color">#FFFFFFFF</color>
 
     <!-- Containers -->
-    <color name="container_fastscroll_thumb_inactive_color">#42000000</color>
+    <color name="container_fastscroll_thumb_inactive_color">#009688</color>
     <color name="container_fastscroll_thumb_active_color">#009688</color>
 
     <!-- All Apps -->
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index b9fb6e2..3f14151 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -55,9 +55,9 @@
     <!-- Notes: container_bounds_inset - quantum_panel_outer_padding -->
     <dimen name="container_bounds_minus_quantum_panel_padding_inset">4dp</dimen>
 
-    <dimen name="container_fastscroll_thumb_min_width">4dp</dimen>
-    <dimen name="container_fastscroll_thumb_max_width">8dp</dimen>
-    <dimen name="container_fastscroll_thumb_height">64dp</dimen>
+    <dimen name="container_fastscroll_thumb_min_width">5dp</dimen>
+    <dimen name="container_fastscroll_thumb_max_width">9dp</dimen>
+    <dimen name="container_fastscroll_thumb_height">72dp</dimen>
     <dimen name="container_fastscroll_thumb_touch_inset">-24dp</dimen>
     <dimen name="container_fastscroll_popup_size">72dp</dimen>
     <dimen name="container_fastscroll_popup_text_size">48dp</dimen>
diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java
index 0fae427..f0d8b3b 100644
--- a/src/com/android/launcher3/BaseRecyclerView.java
+++ b/src/com/android/launcher3/BaseRecyclerView.java
@@ -92,9 +92,15 @@
             // TODO(winsonc): If we want to animate the section heads while scrolling, we can
             //                initiate that here if the recycler view scroll state is not
             //                RecyclerView.SCROLL_STATE_IDLE.
+
+            onUpdateScrollbar(dy);
         }
     }
 
+    public void reset() {
+        mScrollbar.reattachThumbToScroll();
+    }
+
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
@@ -143,7 +149,7 @@
                 mScrollbar.handleTouchEvent(ev, mDownX, mDownY, mLastY);
                 break;
         }
-        return mScrollbar.isDragging();
+        return mScrollbar.isDraggingThumb();
     }
 
     public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
@@ -185,12 +191,10 @@
      *   AvailableScrollHeight = Total height of the all items - last page height
      *
      * This assumes that all rows are the same height.
-     *
-     * @param yOffset the offset from the top of the recycler view to start tracking.
      */
-    protected int getAvailableScrollHeight(int rowCount, int rowHeight, int yOffset) {
+    protected int getAvailableScrollHeight(int rowCount, int rowHeight) {
         int visibleHeight = getHeight() - mBackgroundPadding.top - mBackgroundPadding.bottom;
-        int scrollHeight = getPaddingTop() + yOffset + rowCount * rowHeight + getPaddingBottom();
+        int scrollHeight = getPaddingTop() + rowCount * rowHeight + getPaddingBottom();
         int availableScrollHeight = scrollHeight - visibleHeight;
         return availableScrollHeight;
     }
@@ -222,7 +226,7 @@
     @Override
     protected void dispatchDraw(Canvas canvas) {
         super.dispatchDraw(canvas);
-        onUpdateScrollbar();
+        onUpdateScrollbar(0);
         mScrollbar.draw(canvas);
     }
 
@@ -234,24 +238,21 @@
      * @param scrollPosState the current scroll position
      * @param rowCount the number of rows, used to calculate the total scroll height (assumes that
      *                 all rows are the same height)
-     * @param yOffset the offset to start tracking in the recycler view (only used for all apps)
      */
     protected void synchronizeScrollBarThumbOffsetToViewScroll(ScrollPositionState scrollPosState,
-            int rowCount, int yOffset) {
-        int availableScrollHeight = getAvailableScrollHeight(rowCount, scrollPosState.rowHeight,
-                yOffset);
-        int availableScrollBarHeight = getAvailableScrollBarHeight();
-
+            int rowCount) {
         // Only show the scrollbar if there is height to be scrolled
+        int availableScrollBarHeight = getAvailableScrollBarHeight();
+        int availableScrollHeight = getAvailableScrollHeight(rowCount, scrollPosState.rowHeight);
         if (availableScrollHeight <= 0) {
-            mScrollbar.setScrollbarThumbOffset(-1, -1);
+            mScrollbar.setThumbOffset(-1, -1);
             return;
         }
 
         // Calculate the current scroll position, the scrollY of the recycler view accounts for the
         // view padding, while the scrollBarY is drawn right up to the background padding (ignoring
         // padding)
-        int scrollY = getPaddingTop() + yOffset +
+        int scrollY = getPaddingTop() +
                 (scrollPosState.rowIndex * scrollPosState.rowHeight) - scrollPosState.rowTopOffset;
         int scrollBarY = mBackgroundPadding.top +
                 (int) (((float) scrollY / availableScrollHeight) * availableScrollBarHeight);
@@ -261,9 +262,9 @@
         if (Utilities.isRtl(getResources())) {
             scrollBarX = mBackgroundPadding.left;
         } else {
-            scrollBarX = getWidth() - mBackgroundPadding.right - mScrollbar.getWidth();
+            scrollBarX = getWidth() - mBackgroundPadding.right - mScrollbar.getThumbWidth();
         }
-        mScrollbar.setScrollbarThumbOffset(scrollBarX, scrollBarY);
+        mScrollbar.setThumbOffset(scrollBarX, scrollBarY);
     }
 
     /**
@@ -276,10 +277,15 @@
      * Updates the bounds for the scrollbar.
      * <p>Override in each subclass of this base class.
      */
-    public abstract void onUpdateScrollbar();
+    public abstract void onUpdateScrollbar(int dy);
 
     /**
      * <p>Override in each subclass of this base class.
      */
     public void onFastScrollCompleted() {}
+
+    /**
+     * Returns information about the item that the recycler view is currently scrolled to.
+     */
+    protected abstract void getCurScrollState(ScrollPositionState stateOut);
 }
\ No newline at end of file
diff --git a/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java b/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java
index 2c4184d..f76aed7 100644
--- a/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java
+++ b/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java
@@ -23,6 +23,7 @@
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
+import android.graphics.Path;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.view.MotionEvent;
@@ -51,14 +52,20 @@
     private int mThumbActiveColor;
     @Thunk Point mThumbOffset = new Point(-1, -1);
     @Thunk Paint mThumbPaint;
-    private Paint mTrackPaint;
     private int mThumbMinWidth;
     private int mThumbMaxWidth;
     @Thunk int mThumbWidth;
     @Thunk int mThumbHeight;
+    private int mThumbCurvature;
+    private Path mThumbPath = new Path();
+    private Paint mTrackPaint;
+    private int mTrackWidth;
+    private float mLastTouchY;
     // The inset is the buffer around which a point will still register as a click on the scrollbar
     private int mTouchInset;
     private boolean mIsDragging;
+    private boolean mIsThumbDetached;
+    private boolean mCanThumbDetach;
 
     // This is the offset from the top of the scrollbar when the user first starts touching.  To
     // prevent jumping, this offset is applied as the user scrolls.
@@ -72,51 +79,74 @@
         mPopup = new BaseRecyclerViewFastScrollPopup(rv, res);
         mTrackPaint = new Paint();
         mTrackPaint.setColor(rv.getFastScrollerTrackColor(Color.BLACK));
-        mTrackPaint.setAlpha(0);
+        mTrackPaint.setAlpha(MAX_TRACK_ALPHA);
         mThumbInactiveColor = rv.getFastScrollerThumbInactiveColor(
                 res.getColor(R.color.container_fastscroll_thumb_inactive_color));
         mThumbActiveColor = res.getColor(R.color.container_fastscroll_thumb_active_color);
         mThumbPaint = new Paint();
+        mThumbPaint.setAntiAlias(true);
         mThumbPaint.setColor(mThumbInactiveColor);
+        mThumbPaint.setStyle(Paint.Style.FILL);
         mThumbWidth = mThumbMinWidth = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_min_width);
         mThumbMaxWidth = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_max_width);
         mThumbHeight = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_height);
+        mThumbCurvature = mThumbMaxWidth - mThumbMinWidth;
         mTouchInset = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_touch_inset);
     }
 
-    public void setScrollbarThumbOffset(int x, int y) {
+    public void setDetachThumbOnFastScroll() {
+        mCanThumbDetach = true;
+    }
+
+    public void reattachThumbToScroll() {
+        mIsThumbDetached = false;
+    }
+
+    public void setThumbOffset(int x, int y) {
         if (mThumbOffset.x == x && mThumbOffset.y == y) {
             return;
         }
-        mInvalidateRect.set(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth, mRv.getHeight());
+        mInvalidateRect.set(mThumbOffset.x - mThumbCurvature, mThumbOffset.y,
+                mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);
         mThumbOffset.set(x, y);
-        mInvalidateRect.union(new Rect(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth,
-                mRv.getHeight()));
+        updateThumbPath();
+        mInvalidateRect.union(mThumbOffset.x - mThumbCurvature, mThumbOffset.y,
+                mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);
         mRv.invalidate(mInvalidateRect);
     }
 
-    // Setter/getter for the search bar width for animations
-    public void setWidth(int width) {
-        mInvalidateRect.set(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth, mRv.getHeight());
+    public Point getThumbOffset() {
+        return mThumbOffset;
+    }
+
+    // Setter/getter for the thumb bar width for animations
+    public void setThumbWidth(int width) {
+        mInvalidateRect.set(mThumbOffset.x - mThumbCurvature, mThumbOffset.y,
+                mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);
         mThumbWidth = width;
-        mInvalidateRect.union(new Rect(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth,
-                mRv.getHeight()));
+        updateThumbPath();
+        mInvalidateRect.union(mThumbOffset.x - mThumbCurvature, mThumbOffset.y,
+                mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);
         mRv.invalidate(mInvalidateRect);
     }
 
-    public int getWidth() {
+    public int getThumbWidth() {
         return mThumbWidth;
     }
 
-    // Setter/getter for the track background alpha for animations
-    public void setTrackAlpha(int alpha) {
-        mTrackPaint.setAlpha(alpha);
-        mInvalidateRect.set(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth, mRv.getHeight());
+    // Setter/getter for the track bar width for animations
+    public void setTrackWidth(int width) {
+        mInvalidateRect.set(mThumbOffset.x - mThumbCurvature, 0, mThumbOffset.x + mThumbWidth,
+                mRv.getHeight());
+        mTrackWidth = width;
+        updateThumbPath();
+        mInvalidateRect.union(mThumbOffset.x - mThumbCurvature, 0, mThumbOffset.x + mThumbWidth,
+                mRv.getHeight());
         mRv.invalidate(mInvalidateRect);
     }
 
-    public int getTrackAlpha() {
-        return mTrackPaint.getAlpha();
+    public int getTrackWidth() {
+        return mTrackWidth;
     }
 
     public int getThumbHeight() {
@@ -127,10 +157,18 @@
         return mThumbMaxWidth;
     }
 
-    public boolean isDragging() {
+    public float getLastTouchY() {
+        return mLastTouchY;
+    }
+
+    public boolean isDraggingThumb() {
         return mIsDragging;
     }
 
+    public boolean isThumbDetached() {
+        return mIsThumbDetached;
+    }
+
     /**
      * Handles the touch event and determines whether to show the fast scroller (or updates it if
      * it is already showing).
@@ -152,6 +190,9 @@
                         Math.abs(y - downY) > config.getScaledTouchSlop()) {
                     mRv.getParent().requestDisallowInterceptTouchEvent(true);
                     mIsDragging = true;
+                    if (mCanThumbDetach) {
+                        mIsThumbDetached = true;
+                    }
                     mTouchOffset += (lastY - downY);
                     mPopup.animateVisibility(true);
                     animateScrollbar(true);
@@ -166,11 +207,13 @@
                     mPopup.setSectionName(sectionName);
                     mPopup.animateVisibility(!sectionName.isEmpty());
                     mRv.invalidate(mPopup.updateFastScrollerBounds(mRv, lastY));
+                    mLastTouchY = boundedY;
                 }
                 break;
             case MotionEvent.ACTION_UP:
             case MotionEvent.ACTION_CANCEL:
                 mTouchOffset = 0;
+                mLastTouchY = 0;
                 if (mIsDragging) {
                     mIsDragging = false;
                     mPopup.animateVisibility(false);
@@ -189,8 +232,7 @@
         if (mTrackPaint.getAlpha() > 0) {
             canvas.drawRect(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth, mRv.getHeight(), mTrackPaint);
         }
-        canvas.drawRect(mThumbOffset.x, mThumbOffset.y, mThumbOffset.x + mThumbWidth,
-                mThumbOffset.y + mThumbHeight, mThumbPaint);
+        canvas.drawPath(mThumbPath, mThumbPaint);
 
         // Draw the popup
         mPopup.draw(canvas);
@@ -203,27 +245,46 @@
         if (mScrollbarAnimator != null) {
             mScrollbarAnimator.cancel();
         }
-        ObjectAnimator trackAlphaAnim = ObjectAnimator.ofInt(this, "trackAlpha",
-                isScrolling ? MAX_TRACK_ALPHA : 0);
-        ObjectAnimator thumbWidthAnim = ObjectAnimator.ofInt(this, "width",
-                isScrolling ? mThumbMaxWidth : mThumbMinWidth);
-        ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(),
-                mThumbPaint.getColor(), isScrolling ? mThumbActiveColor : mThumbInactiveColor);
-        colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animator) {
-                mThumbPaint.setColor((Integer) animator.getAnimatedValue());
-                mRv.invalidate(mThumbOffset.x, mThumbOffset.y, mThumbOffset.x + mThumbWidth,
-                        mThumbOffset.y + mThumbHeight);
-            }
-        });
+
         mScrollbarAnimator = new AnimatorSet();
-        mScrollbarAnimator.playTogether(trackAlphaAnim, thumbWidthAnim, colorAnimation);
+        ObjectAnimator trackWidthAnim = ObjectAnimator.ofInt(this, "trackWidth",
+                isScrolling ? mThumbMaxWidth : mThumbMinWidth);
+        ObjectAnimator thumbWidthAnim = ObjectAnimator.ofInt(this, "thumbWidth",
+                isScrolling ? mThumbMaxWidth : mThumbMinWidth);
+        mScrollbarAnimator.playTogether(trackWidthAnim, thumbWidthAnim);
+        if (mThumbActiveColor != mThumbInactiveColor) {
+            ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(),
+                    mThumbPaint.getColor(), isScrolling ? mThumbActiveColor : mThumbInactiveColor);
+            colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+                @Override
+                public void onAnimationUpdate(ValueAnimator animator) {
+                    mThumbPaint.setColor((Integer) animator.getAnimatedValue());
+                    mRv.invalidate(mThumbOffset.x, mThumbOffset.y, mThumbOffset.x + mThumbWidth,
+                            mThumbOffset.y + mThumbHeight);
+                }
+            });
+            mScrollbarAnimator.play(colorAnimation);
+        }
         mScrollbarAnimator.setDuration(SCROLL_BAR_VIS_DURATION);
         mScrollbarAnimator.start();
     }
 
     /**
+     * Updates the path for the thumb drawable.
+     */
+    private void updateThumbPath() {
+        mThumbCurvature = mThumbMaxWidth - mThumbWidth;
+        mThumbPath.reset();
+        mThumbPath.moveTo(mThumbOffset.x + mThumbWidth, mThumbOffset.y);                    // tr
+        mThumbPath.lineTo(mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);     // br
+        mThumbPath.lineTo(mThumbOffset.x, mThumbOffset.y + mThumbHeight);                   // bl
+        mThumbPath.cubicTo(mThumbOffset.x, mThumbOffset.y + mThumbHeight,
+                mThumbOffset.x - mThumbCurvature, mThumbOffset.y + mThumbHeight / 2,
+                mThumbOffset.x, mThumbOffset.y);                                            // bl2tl
+        mThumbPath.close();
+    }
+
+    /**
      * Returns whether the specified points are near the scroll bar bounds.
      */
     private boolean isNearPoint(int x, int y) {
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 1bcaab5..5070878 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -510,7 +510,7 @@
             mIcon.setBounds(0, 0, iconSize, iconSize);
         }
         if (mLayoutHorizontal) {
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+            if (Utilities.ATLEAST_JB_MR1) {
                 setCompoundDrawablesRelative(mIcon, null, null, null);
             } else {
                 setCompoundDrawables(mIcon, null, null, null);
diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java
index b7f89d0..2baa6d8 100644
--- a/src/com/android/launcher3/ButtonDropTarget.java
+++ b/src/com/android/launcher3/ButtonDropTarget.java
@@ -24,7 +24,6 @@
 import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.res.ColorStateList;
-import android.content.res.Configuration;
 import android.graphics.ColorMatrix;
 import android.graphics.ColorMatrixColorFilter;
 import android.graphics.PointF;
@@ -93,7 +92,7 @@
         // drawableLeft and drawableStart.
         mDrawable = getResources().getDrawable(resId);
 
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+        if (Utilities.ATLEAST_JB_MR1) {
             setCompoundDrawablesRelativeWithIntrinsicBounds(mDrawable, null, null, null);
         } else {
             setCompoundDrawablesWithIntrinsicBounds(mDrawable, null, null, null);
@@ -114,7 +113,7 @@
     @Override
     public final void onDragEnter(DragObject d) {
         d.dragView.setColor(mHoverColor);
-        if (Utilities.isLmpOrAbove()) {
+        if (Utilities.ATLEAST_LOLLIPOP) {
             animateTextColor(mHoverColor);
         } else {
             if (mCurrentFilter == null) {
@@ -131,8 +130,8 @@
         // Do nothing
     }
 
-	protected void resetHoverColor() {
-        if (Utilities.isLmpOrAbove()) {
+    protected void resetHoverColor() {
+        if (Utilities.ATLEAST_LOLLIPOP) {
             animateTextColor(mOriginalTextColor.getDefaultColor());
         } else {
             mDrawable.setColorFilter(null);
diff --git a/src/com/android/launcher3/DragView.java b/src/com/android/launcher3/DragView.java
index dfa8202..2acfc61 100644
--- a/src/com/android/launcher3/DragView.java
+++ b/src/com/android/launcher3/DragView.java
@@ -131,7 +131,7 @@
         measure(ms, ms);
         mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
 
-        if (Utilities.isLmpOrAbove()) {
+        if (Utilities.ATLEAST_LOLLIPOP) {
             setElevation(getResources().getDimension(R.dimen.drag_elevation));
         }
     }
@@ -252,14 +252,14 @@
             setColorScale(color, m2);
             m1.postConcat(m2);
 
-            if (Utilities.isLmpOrAbove()) {
+            if (Utilities.ATLEAST_LOLLIPOP) {
                 animateFilterTo(m1.getArray());
             } else {
                 mPaint.setColorFilter(new ColorMatrixColorFilter(m1));
                 invalidate();
             }
         } else {
-            if (!Utilities.isLmpOrAbove() || mCurrentFilter == null) {
+            if (!Utilities.ATLEAST_LOLLIPOP || mCurrentFilter == null) {
                 mPaint.setColorFilter(null);
                 invalidate();
             } else {
diff --git a/src/com/android/launcher3/Folder.java b/src/com/android/launcher3/Folder.java
index 7b88a88..c1aa356 100644
--- a/src/com/android/launcher3/Folder.java
+++ b/src/com/android/launcher3/Folder.java
@@ -453,7 +453,7 @@
 
         Animator openFolderAnim = null;
         final Runnable onCompleteRunnable;
-        if (!Utilities.isLmpOrAbove()) {
+        if (!Utilities.ATLEAST_LOLLIPOP) {
             positionAndSizeAsIcon();
             centerAboutIcon();
 
@@ -568,7 +568,7 @@
                 public void onAnimationEnd(Animator animation) {
                     mFolderName.animate().setDuration(FOLDER_NAME_ANIMATION_DURATION)
                         .translationX(0)
-                        .setInterpolator(Utilities.isLmpOrAbove() ?
+                        .setInterpolator(Utilities.ATLEAST_LOLLIPOP ?
                                 AnimationUtils.loadInterpolator(mLauncher,
                                         android.R.interpolator.fast_out_slow_in)
                                 : new LogDecelerateInterpolator(100, 0));
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 19ddeec..6f2458c 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -116,8 +116,6 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.PrintWriter;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
 import java.text.DateFormat;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -360,18 +358,6 @@
         }
     }
 
-    // TODO: remove this field and call method directly when Launcher3 can depend on M APIs
-    private static Method sClipRevealMethod = null;
-    static {
-        Class<?> activityOptionsClass = ActivityOptions.class;
-        try {
-            sClipRevealMethod = activityOptionsClass.getDeclaredMethod("makeClipRevealAnimation",
-                    View.class, int.class, int.class, int.class, int.class);
-        } catch (Exception e) {
-            // Earlier version
-        }
-    }
-
     @Thunk Runnable mBuildLayersRunnable = new Runnable() {
         public void run() {
             if (mWorkspace != null) {
@@ -667,7 +653,7 @@
 
     @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
     public static int generateViewId() {
-        if (Build.VERSION.SDK_INT >= 17) {
+        if (Utilities.ATLEAST_JB_MR1) {
             return View.generateViewId();
         } else {
             // View.generateViewId() is not available. The following fallback logic is a copy
@@ -1653,18 +1639,18 @@
         }
         registerReceiver(mReceiver, filter);
         FirstFrameAnimatorHelper.initializeDrawListener(getWindow().getDecorView());
-        setupTransparentSystemBarsForLmp();
+        setupTransparentSystemBarsForLollipop();
         mAttached = true;
         mVisible = true;
     }
 
     /**
-     * Sets up transparent navigation and status bars in LMP.
+     * Sets up transparent navigation and status bars in Lollipop.
      * This method is a no-op for other platform versions.
      */
     @TargetApi(Build.VERSION_CODES.LOLLIPOP)
-    private void setupTransparentSystemBarsForLmp() {
-        if (Utilities.isLmpOrAbove()) {
+    private void setupTransparentSystemBarsForLollipop() {
+        if (Utilities.ATLEAST_LOLLIPOP) {
             Window window = getWindow();
             window.getAttributes().systemUiVisibility |=
                     (View.SYSTEM_UI_FLAG_LAYOUT_STABLE
@@ -1867,29 +1853,22 @@
         super.onNewIntent(intent);
 
         // Close the menu
-        if (Intent.ACTION_MAIN.equals(intent.getAction())) {
+        Folder openFolder = mWorkspace.getOpenFolder();
+        boolean alreadyOnHome = mHasFocus && ((intent.getFlags() &
+                Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT)
+                != Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
+        boolean isActionMain = Intent.ACTION_MAIN.equals(intent.getAction());
+        if (isActionMain) {
             // also will cancel mWaitingForResult.
             closeSystemDialogs();
 
-            final boolean alreadyOnHome = mHasFocus && ((intent.getFlags() &
-                    Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT)
-                    != Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
-
             if (mWorkspace == null) {
                 // Can be cases where mWorkspace is null, this prevents a NPE
                 return;
             }
-            Folder openFolder = mWorkspace.getOpenFolder();
             // In all these cases, only animate if we're already on home
             mWorkspace.exitWidgetResizeMode();
 
-            boolean moveToDefaultScreen = mLauncherCallbacks != null ?
-                    mLauncherCallbacks.shouldMoveToDefaultScreenOnHomeIntent() : true;
-            if (alreadyOnHome && mState == State.WORKSPACE && !mWorkspace.isTouchActive() &&
-                    openFolder == null && moveToDefaultScreen) {
-                mWorkspace.moveToDefaultScreen(true);
-            }
-
             closeFolder();
             exitSpringLoadedDragMode();
 
@@ -1923,13 +1902,30 @@
             }
         }
 
-        if (DEBUG_RESUME_TIME) {
-            Log.d(TAG, "Time spent in onNewIntent: " + (System.currentTimeMillis() - startTime));
-        }
-
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.onNewIntent(intent);
         }
+
+        // Defer moving to the default screen until after we callback to the LauncherCallbacks
+        // as slow logic in the callbacks eat into the time the scroller expects for the snapToPage
+        // animation.
+        if (isActionMain) {
+            boolean moveToDefaultScreen = mLauncherCallbacks != null ?
+                    mLauncherCallbacks.shouldMoveToDefaultScreenOnHomeIntent() : true;
+            if (alreadyOnHome && mState == State.WORKSPACE && !mWorkspace.isTouchActive() &&
+                    openFolder == null && moveToDefaultScreen) {
+                mWorkspace.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        mWorkspace.moveToDefaultScreen(true);
+                    }
+                });
+            }
+        }
+
+        if (DEBUG_RESUME_TIME) {
+            Log.d(TAG, "Time spent in onNewIntent: " + (System.currentTimeMillis() - startTime));
+        }
     }
 
     @Override
@@ -2883,8 +2879,7 @@
             Bundle optsBundle = null;
             if (useLaunchAnimation) {
                 ActivityOptions opts = null;
-                if (sClipRevealMethod != null) {
-                    // TODO: call method directly when Launcher3 can depend on M APIs
+                if (Utilities.ATLEAST_MARSHMALLOW) {
                     int left = 0, top = 0;
                     int width = v.getMeasuredWidth(), height = v.getMeasuredHeight();
                     if (v instanceof TextView) {
@@ -2898,22 +2893,12 @@
                             height = bounds.height();
                         }
                     }
-                    try {
-                        opts = (ActivityOptions) sClipRevealMethod.invoke(null, v,
-                                left, top, width, height);
-                    } catch (IllegalAccessException e) {
-                        Log.d(TAG, "Could not call makeClipRevealAnimation: " + e);
-                        sClipRevealMethod = null;
-                    } catch (InvocationTargetException e) {
-                        Log.d(TAG, "Could not call makeClipRevealAnimation: " + e);
-                        sClipRevealMethod = null;
-                    }
-                }
-                if (opts == null && !Utilities.isLmpOrAbove()) {
+                    opts = ActivityOptions.makeClipRevealAnimation(v, left, top, width, height);
+                } else if (!Utilities.ATLEAST_LOLLIPOP) {
                     // Below L, we use a scale up animation
                     opts = ActivityOptions.makeScaleUpAnimation(v, 0, 0,
                                     v.getMeasuredWidth(), v.getMeasuredHeight());
-                } else if (opts == null && Utilities.isLmpMR1()) {
+                } else if (Utilities.ATLEAST_LOLLIPOP_MR1) {
                     // On L devices, we use the device default slide-up transition.
                     // On L MR1 devices, we a custom version of the slide-up transition which
                     // doesn't have the delay present in the device default.
@@ -3028,7 +3013,7 @@
 
         ObjectAnimator oa = LauncherAnimUtils.ofPropertyValuesHolder(mFolderIconImageView, alpha,
                 scaleX, scaleY);
-        if (Utilities.isLmpOrAbove()) {
+        if (Utilities.ATLEAST_LOLLIPOP) {
             oa.setInterpolator(new LogDecelerateInterpolator(100, 0));
         }
         oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration));
@@ -4324,13 +4309,14 @@
         return oriMap[(d.getRotation() + indexOffset) % 4];
     }
 
+    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
     public void lockScreenOrientation() {
         if (mRotationEnabled) {
-            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
+            if (Utilities.ATLEAST_JB_MR2) {
+                setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
+            } else {
                 setRequestedOrientation(mapConfigurationOriActivityInfoOri(getResources()
                         .getConfiguration().orientation));
-            } else {
-                setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
             }
         }
     }
@@ -4534,7 +4520,7 @@
     public ItemInfo createAppDragInfo(Intent appLaunchIntent) {
         // Called from search suggestion
         UserHandleCompat user = null;
-        if (Utilities.isLmpOrAbove()) {
+        if (Utilities.ATLEAST_LOLLIPOP) {
             UserHandle userHandle = appLaunchIntent.getParcelableExtra(Intent.EXTRA_USER);
             if (userHandle != null) {
                 user = UserHandleCompat.fromUser(userHandle);
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 52f85ea..d87ad67 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -127,7 +127,7 @@
     LauncherModel setLauncher(Launcher launcher) {
         getLauncherProvider().setLauncherProviderChangeListener(launcher);
         mModel.initialize(launcher);
-        mAccessibilityDelegate = ((launcher != null) && Utilities.isLmpOrAbove()) ?
+        mAccessibilityDelegate = ((launcher != null) && Utilities.ATLEAST_LOLLIPOP) ?
             new LauncherAccessibilityDelegate(launcher) : null;
         return mModel;
     }
diff --git a/src/com/android/launcher3/LauncherBackupAgentHelper.java b/src/com/android/launcher3/LauncherBackupAgentHelper.java
index 3debef6..8eb4e63 100644
--- a/src/com/android/launcher3/LauncherBackupAgentHelper.java
+++ b/src/com/android/launcher3/LauncherBackupAgentHelper.java
@@ -65,7 +65,7 @@
     @Override
     public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
             throws IOException {
-        if (!Utilities.isLmpOrAbove()) {
+        if (!Utilities.ATLEAST_LOLLIPOP) {
             // No restore for old devices.
             Log.i(TAG, "You shall not pass!!!");
             Log.d(TAG, "Restore is only supported on devices running Lollipop and above.");
diff --git a/src/com/android/launcher3/LauncherClings.java b/src/com/android/launcher3/LauncherClings.java
index c44ea6b..7470284 100644
--- a/src/com/android/launcher3/LauncherClings.java
+++ b/src/com/android/launcher3/LauncherClings.java
@@ -16,8 +16,6 @@
 
 package com.android.launcher3;
 
-import android.accounts.Account;
-import android.accounts.AccountManager;
 import android.animation.ObjectAnimator;
 import android.animation.PropertyValuesHolder;
 import android.annotation.TargetApi;
@@ -36,6 +34,7 @@
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver.OnGlobalLayoutListener;
 import android.view.accessibility.AccessibilityManager;
+
 import com.android.launcher3.util.Thunk;
 
 class LauncherClings implements OnClickListener {
@@ -225,7 +224,7 @@
 
         // Restricted secondary users (child mode) will potentially have very few apps
         // seeded when they start up for the first time. Clings won't work well with that
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
+        if (Utilities.ATLEAST_JB_MR2) {
             UserManager um = (UserManager) mLauncher.getSystemService(Context.USER_SERVICE);
             Bundle restrictions = um.getUserRestrictions();
             if (restrictions.getBoolean(UserManager.DISALLOW_MODIFY_ACCOUNTS, false)) {
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index 0599714..20844ec 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -402,7 +402,7 @@
     @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
     private AutoInstallsLayout createWorkspaceLoaderFromAppRestriction() {
         // UserManager.getApplicationRestrictions() requires minSdkVersion >= 18
-        if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
+        if (!Utilities.ATLEAST_JB_MR2) {
             return null;
         }
 
diff --git a/src/com/android/launcher3/LauncherStateTransitionAnimation.java b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
index 5a3f3dc..cdde8c1 100644
--- a/src/com/android/launcher3/LauncherStateTransitionAnimation.java
+++ b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
@@ -205,7 +205,7 @@
             final boolean animated, final PrivateTransitionCallbacks pCb) {
         final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
         final Resources res = mLauncher.getResources();
-        final boolean material = Utilities.isLmpOrAbove();
+        final boolean material = Utilities.ATLEAST_LOLLIPOP;
         final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime);
         final int itemsAlphaStagger =
                 res.getInteger(R.integer.config_overlayItemsAlphaStagger);
@@ -358,12 +358,11 @@
                     dispatchOnLauncherTransitionStart(toView, animated, false);
 
                     // Enable all necessary layers
-                    boolean isLmpOrAbove = Utilities.isLmpOrAbove();
                     for (View v : layerViews.keySet()) {
                         if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
                             v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
                         }
-                        if (isLmpOrAbove && Utilities.isViewAttachedToWindow(v)) {
+                        if (Utilities.ATLEAST_LOLLIPOP && Utilities.isViewAttachedToWindow(v)) {
                             v.buildLayer();
                         }
                     }
@@ -486,7 +485,7 @@
             final PrivateTransitionCallbacks pCb) {
         final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
         final Resources res = mLauncher.getResources();
-        final boolean material = Utilities.isLmpOrAbove();
+        final boolean material = Utilities.ATLEAST_LOLLIPOP;
         final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime);
         final int itemsAlphaStagger =
                 res.getInteger(R.integer.config_overlayItemsAlphaStagger);
@@ -668,12 +667,11 @@
                     dispatchOnLauncherTransitionStart(toView, animated, false);
 
                     // Enable all necessary layers
-                    boolean isLmpOrAbove = Utilities.isLmpOrAbove();
                     for (View v : layerViews.keySet()) {
                         if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
                             v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
                         }
-                        if (isLmpOrAbove && Utilities.isViewAttachedToWindow(v)) {
+                        if (Utilities.ATLEAST_LOLLIPOP && Utilities.isViewAttachedToWindow(v)) {
                             v.buildLayer();
                         }
                     }
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 218c1a3..05f0a05 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -2293,7 +2293,7 @@
         // Besides disabling the accessibility long-click, this also prevents this view from getting
         // accessibility focus.
         info.setLongClickable(false);
-        if (Utilities.isLmpOrAbove()) {
+        if (Utilities.ATLEAST_LOLLIPOP) {
             info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK);
         }
     }
diff --git a/src/com/android/launcher3/PendingAppWidgetHostView.java b/src/com/android/launcher3/PendingAppWidgetHostView.java
index e8c8b8e..40eadab 100644
--- a/src/com/android/launcher3/PendingAppWidgetHostView.java
+++ b/src/com/android/launcher3/PendingAppWidgetHostView.java
@@ -78,7 +78,7 @@
         setBackgroundResource(R.drawable.quantum_panel_dark);
         setWillNotDraw(false);
 
-        if (Utilities.isLmpOrAbove()) {
+        if (Utilities.ATLEAST_LOLLIPOP) {
             setElevation(getResources().getDimension(R.dimen.pending_widget_elevation));
         }
     }
diff --git a/src/com/android/launcher3/SearchDropTargetBar.java b/src/com/android/launcher3/SearchDropTargetBar.java
index 1efdfb6..772a334 100644
--- a/src/com/android/launcher3/SearchDropTargetBar.java
+++ b/src/com/android/launcher3/SearchDropTargetBar.java
@@ -186,7 +186,12 @@
      */
     private void animateViewAlpha(LauncherViewPropertyAnimator animator, View v, float alpha,
             int duration) {
-        if (v != null && Float.compare(v.getAlpha(), alpha) != 0) {
+        if (v == null) {
+            return;
+        }
+
+        animator.cancel();
+        if (Float.compare(v.getAlpha(), alpha) != 0) {
             if (duration > 0) {
                 animator.alpha(alpha).withLayer().setDuration(duration).start();
             } else {
diff --git a/src/com/android/launcher3/UninstallDropTarget.java b/src/com/android/launcher3/UninstallDropTarget.java
index 0819f8c..955d401 100644
--- a/src/com/android/launcher3/UninstallDropTarget.java
+++ b/src/com/android/launcher3/UninstallDropTarget.java
@@ -38,7 +38,7 @@
 
     @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
     public static boolean supportsDrop(Context context, Object info) {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
+        if (Utilities.ATLEAST_JB_MR2) {
             UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
             Bundle restrictions = userManager.getUserRestrictions();
             if (restrictions.getBoolean(UserManager.DISALLOW_APPS_CONTROL, false)
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 8fd298d..adedd33 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -87,6 +87,24 @@
     private static final int[] sLoc0 = new int[2];
     private static final int[] sLoc1 = new int[2];
 
+    // TODO: use Build.VERSION_CODES when available
+    public static final boolean ATLEAST_MARSHMALLOW = Build.VERSION.SDK_INT >= 23;
+
+    public static final boolean ATLEAST_LOLLIPOP_MR1 =
+            Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1;
+
+    public static final boolean ATLEAST_LOLLIPOP =
+            Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
+
+    public static final boolean ATLEAST_KITKAT =
+            Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
+
+    public static final boolean ATLEAST_JB_MR1 =
+            Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1;
+
+    public static final boolean ATLEAST_JB_MR2 =
+            Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2;
+
     // To turn on these properties, type
     // adb shell setprop log.tag.PROPERTY_NAME [VERBOSE | SUPPRESS]
     private static final String FORCE_ENABLE_ROTATION_PROPERTY = "launcher_force_rotate";
@@ -110,23 +128,6 @@
         return sForceEnableRotation || context.getResources().getBoolean(R.bool.allow_rotation);
     }
 
-    /**
-     * Indicates if the device is running LMP or higher.
-     */
-    public static boolean isLmpOrAbove() {
-        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
-    }
-
-    public static boolean isLmpMR1OrAbove() {
-        // TODO(adamcohen): update to Build.VERSION_CODES.LOLLIPOP_MR1 once building against 22;
-        return Build.VERSION.SDK_INT >= 22;
-    }
-
-    public static boolean isLmpMR1() {
-        // TODO(adamcohen): update to Build.VERSION_CODES.LOLLIPOP_MR1 once building against 22;
-        return Build.VERSION.SDK_INT == 22;
-    }
-
     public static Bitmap createIconBitmap(Cursor c, int iconIndex, Context context) {
         byte[] data = c.getBlob(iconIndex);
         try {
@@ -517,7 +518,7 @@
 
     @TargetApi(Build.VERSION_CODES.KITKAT)
     public static boolean isViewAttachedToWindow(View v) {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+        if (ATLEAST_KITKAT) {
             return v.isAttachedToWindow();
         } else {
             // A proxy call which returns null, if the view is not attached to the window.
@@ -544,7 +545,7 @@
         AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
         for (AppWidgetProviderInfo info : appWidgetManager.getInstalledProviders()) {
             if (info.provider.getPackageName().equals(providerPkg)) {
-                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+                if (ATLEAST_JB_MR1) {
                     if ((info.widgetCategory & AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX) != 0) {
                         return info;
                     } else if (defaultWidgetForSearchPackage == null) {
@@ -655,7 +656,7 @@
 
     @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
     public static boolean isRtl(Resources res) {
-        return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) &&
+        return ATLEAST_JB_MR1 &&
                 (res.getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
     }
 
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 08e6429..44d70ba 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -2013,7 +2013,7 @@
     }
 
     public void updateAccessibilityFlags() {
-        if (Utilities.isLmpOrAbove()) {
+        if (Utilities.ATLEAST_LOLLIPOP) {
             int total = getPageCount();
             for (int i = numCustomPages(); i < total; i++) {
                 updateAccessibilityFlags((CellLayout) getPageAt(i), i);
@@ -2319,14 +2319,14 @@
             throw new IllegalStateException(msg);
         }
 
-        DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
-                DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale, accessible);
-        dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor());
-
         if (child.getParent() instanceof ShortcutAndWidgetContainer) {
             mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent();
         }
 
+        DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
+                DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale, accessible);
+        dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor());
+
         b.recycle();
     }
 
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index e129dc6..6d008ab 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -555,8 +555,9 @@
     @Override
     public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
         if (toWorkspace) {
-            // Reset the search bar after transitioning home
+            // Reset the search bar and base recycler view after transitioning home
             mSearchBarController.reset();
+            mAppsRecyclerView.reset();
         }
     }
 
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index 5aa973a..5ec8bb2 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -72,6 +72,7 @@
     public AllAppsRecyclerView(Context context, AttributeSet attrs, int defStyleAttr,
             int defStyleRes) {
         super(context, attrs, defStyleAttr);
+        mScrollbar.setDetachThumbOnFastScroll();
     }
 
     /**
@@ -168,8 +169,8 @@
         }
 
         // Map the touch position back to the scroll of the recycler view
-        getCurScrollState(mScrollPosState, mApps.getAdapterItems());
-        int availableScrollHeight = getAvailableScrollHeight(rowCount, mScrollPosState.rowHeight, 0);
+        getCurScrollState(mScrollPosState);
+        int availableScrollHeight = getAvailableScrollHeight(rowCount, mScrollPosState.rowHeight);
         LinearLayoutManager layoutManager = (LinearLayoutManager) getLayoutManager();
         if (mFastScrollMode == FAST_SCROLL_MODE_FREE_SCROLL) {
             layoutManager.scrollToPositionWithOffset(0, (int) -(availableScrollHeight * touchFraction));
@@ -216,24 +217,73 @@
      * Updates the bounds for the scrollbar.
      */
     @Override
-    public void onUpdateScrollbar() {
+    public void onUpdateScrollbar(int dy) {
         List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
 
         // Skip early if there are no items or we haven't been measured
         if (items.isEmpty() || mNumAppsPerRow == 0) {
-            mScrollbar.setScrollbarThumbOffset(-1, -1);
+            mScrollbar.setThumbOffset(-1, -1);
             return;
         }
 
         // Find the index and height of the first visible row (all rows have the same height)
         int rowCount = mApps.getNumAppRows();
-        getCurScrollState(mScrollPosState, items);
+        getCurScrollState(mScrollPosState);
         if (mScrollPosState.rowIndex < 0) {
-            mScrollbar.setScrollbarThumbOffset(-1, -1);
+            mScrollbar.setThumbOffset(-1, -1);
             return;
         }
 
-        synchronizeScrollBarThumbOffsetToViewScroll(mScrollPosState, rowCount, 0);
+        // Only show the scrollbar if there is height to be scrolled
+        int availableScrollBarHeight = getAvailableScrollBarHeight();
+        int availableScrollHeight = getAvailableScrollHeight(mApps.getNumAppRows(), mScrollPosState.rowHeight);
+        if (availableScrollHeight <= 0) {
+            mScrollbar.setThumbOffset(-1, -1);
+            return;
+        }
+
+        // Calculate the current scroll position, the scrollY of the recycler view accounts for the
+        // view padding, while the scrollBarY is drawn right up to the background padding (ignoring
+        // padding)
+        int scrollY = getPaddingTop() +
+                (mScrollPosState.rowIndex * mScrollPosState.rowHeight) - mScrollPosState.rowTopOffset;
+        int scrollBarY = mBackgroundPadding.top +
+                (int) (((float) scrollY / availableScrollHeight) * availableScrollBarHeight);
+
+        if (mScrollbar.isThumbDetached()) {
+            int scrollBarX;
+            if (Utilities.isRtl(getResources())) {
+                scrollBarX = mBackgroundPadding.left;
+            } else {
+                scrollBarX = getWidth() - mBackgroundPadding.right - mScrollbar.getThumbWidth();
+            }
+
+            if (mScrollbar.isDraggingThumb()) {
+                // If the thumb is detached, then just update the thumb to the current
+                // touch position
+                mScrollbar.setThumbOffset(scrollBarX, (int) mScrollbar.getLastTouchY());
+            } else {
+                int thumbScrollY = mScrollbar.getThumbOffset().y;
+                int diffScrollY = scrollBarY - thumbScrollY;
+                if (diffScrollY * dy > 0f) {
+                    // User is scrolling in the same direction the thumb needs to catch up to the
+                    // current scroll position.
+                    thumbScrollY += dy < 0 ? Math.max(dy, diffScrollY) : Math.min(dy, diffScrollY);
+                    thumbScrollY = Math.max(0, Math.min(availableScrollBarHeight, thumbScrollY));
+                    mScrollbar.setThumbOffset(scrollBarX, thumbScrollY);
+                    if (scrollBarY == thumbScrollY) {
+                        mScrollbar.reattachThumbToScroll();
+                    }
+                } else {
+                    // User is scrolling in an opposite direction to the direction that the thumb
+                    // needs to catch up to the scroll position.  Do nothing except for updating
+                    // the scroll bar x to match the thumb width.
+                    mScrollbar.setThumbOffset(scrollBarX, thumbScrollY);
+                }
+            }
+        } else {
+            synchronizeScrollBarThumbOffsetToViewScroll(mScrollPosState, rowCount);
+        }
     }
 
     /**
@@ -285,13 +335,13 @@
     /**
      * Returns the current scroll state of the apps rows.
      */
-    private void getCurScrollState(ScrollPositionState stateOut,
-            List<AlphabeticalAppsList.AdapterItem> items) {
+    protected void getCurScrollState(ScrollPositionState stateOut) {
         stateOut.rowIndex = -1;
         stateOut.rowTopOffset = -1;
         stateOut.rowHeight = -1;
 
         // Return early if there are no items or we haven't been measured
+        List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
         if (items.isEmpty() || mNumAppsPerRow == 0) {
             return;
         }
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index 396f757..dac0df1 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -43,6 +43,11 @@
     private static final boolean DEBUG = false;
     private static final boolean DEBUG_PREDICTIONS = false;
 
+    private static final int FAST_SCROLL_FRACTION_DISTRIBUTE_BY_ROWS_FRACTION = 0;
+    private static final int FAST_SCROLL_FRACTION_DISTRIBUTE_BY_NUM_SECTIONS = 1;
+
+    private final int mFastScrollDistributionMode = FAST_SCROLL_FRACTION_DISTRIBUTE_BY_NUM_SECTIONS;
+
     /**
      * Info about a section in the alphabetic list
      */
@@ -85,8 +90,6 @@
         /** Section & App properties */
         // The section for this item
         public SectionInfo sectionInfo;
-        // The row that this item shows up on
-        public int rowIndex;
 
         /** App-only properties */
         // The section name of this app.  Note that there can be multiple items with different
@@ -94,6 +97,8 @@
         public String sectionName = null;
         // The index of this app in the section
         public int sectionAppIndex = -1;
+        // The row that this item shows up on
+        public int rowIndex;
         // The index of this app in the row
         public int rowAppIndex;
         // The associated AppInfo for the app
@@ -188,7 +193,6 @@
     private int mNumAppsPerRow;
     private int mNumPredictedAppsPerRow;
     private int mNumAppRowsInAdapter;
-    private boolean mDisableEmptyText;
 
     public AlphabeticalAppsList(Context context) {
         mLauncher = (Launcher) context;
@@ -216,13 +220,6 @@
     }
 
     /**
-     * Disables the empty text message when there are no search results.
-     */
-    public void disableEmptyText() {
-        mDisableEmptyText = true;
-    }
-
-    /**
      * Returns all the apps.
      */
     public List<AppInfo> getApps() {
@@ -523,18 +520,36 @@
             }
             mNumAppRowsInAdapter = rowIndex + 1;
 
-            // Pre-calculate all the fast scroller fractions based on the number of rows
-            float rowFraction = 1f / mNumAppRowsInAdapter;
-            for (FastScrollSectionInfo info : mFastScrollerSections) {
-                AdapterItem item = info.fastScrollToItem;
-                if (item.viewType != AllAppsGridAdapter.ICON_VIEW_TYPE &&
-                        item.viewType != AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE) {
-                    info.touchFraction = 0f;
-                    continue;
-                }
+            // Pre-calculate all the fast scroller fractions
+            switch (mFastScrollDistributionMode) {
+                case FAST_SCROLL_FRACTION_DISTRIBUTE_BY_ROWS_FRACTION:
+                    float rowFraction = 1f / mNumAppRowsInAdapter;
+                    for (FastScrollSectionInfo info : mFastScrollerSections) {
+                        AdapterItem item = info.fastScrollToItem;
+                        if (item.viewType != AllAppsGridAdapter.ICON_VIEW_TYPE &&
+                                item.viewType != AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE) {
+                            info.touchFraction = 0f;
+                            continue;
+                        }
 
-                float subRowFraction = item.rowAppIndex * (rowFraction / mNumAppsPerRow);
-                info.touchFraction = item.rowIndex * rowFraction + subRowFraction;
+                        float subRowFraction = item.rowAppIndex * (rowFraction / mNumAppsPerRow);
+                        info.touchFraction = item.rowIndex * rowFraction + subRowFraction;
+                    }
+                    break;
+                case FAST_SCROLL_FRACTION_DISTRIBUTE_BY_NUM_SECTIONS:
+                    float perSectionTouchFraction = 1f / mFastScrollerSections.size();
+                    float cumulativeTouchFraction = 0f;
+                    for (FastScrollSectionInfo info : mFastScrollerSections) {
+                        AdapterItem item = info.fastScrollToItem;
+                        if (item.viewType != AllAppsGridAdapter.ICON_VIEW_TYPE &&
+                                item.viewType != AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE) {
+                            info.touchFraction = 0f;
+                            continue;
+                        }
+                        info.touchFraction = cumulativeTouchFraction;
+                        cumulativeTouchFraction += perSectionTouchFraction;
+                    }
+                    break;
             }
         }
 
diff --git a/src/com/android/launcher3/compat/AppWidgetManagerCompat.java b/src/com/android/launcher3/compat/AppWidgetManagerCompat.java
index 7aa36d4..434f13d 100644
--- a/src/com/android/launcher3/compat/AppWidgetManagerCompat.java
+++ b/src/com/android/launcher3/compat/AppWidgetManagerCompat.java
@@ -40,7 +40,7 @@
     public static AppWidgetManagerCompat getInstance(Context context) {
         synchronized (sInstanceLock) {
             if (sInstance == null) {
-                if (Utilities.isLmpOrAbove()) {
+                if (Utilities.ATLEAST_LOLLIPOP) {
                     sInstance = new AppWidgetManagerCompatVL(context.getApplicationContext());
                 } else {
                     sInstance = new AppWidgetManagerCompatV16(context.getApplicationContext());
diff --git a/src/com/android/launcher3/compat/AppWidgetManagerCompatV16.java b/src/com/android/launcher3/compat/AppWidgetManagerCompatV16.java
index f7f4b7e..463cf90 100644
--- a/src/com/android/launcher3/compat/AppWidgetManagerCompatV16.java
+++ b/src/com/android/launcher3/compat/AppWidgetManagerCompatV16.java
@@ -54,10 +54,10 @@
     @Override
     public boolean bindAppWidgetIdIfAllowed(int appWidgetId, AppWidgetProviderInfo info,
             Bundle options) {
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
-            return mAppWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, info.provider);
-        } else {
+        if (Utilities.ATLEAST_JB_MR1) {
             return mAppWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, info.provider, options);
+        } else {
+            return mAppWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, info.provider);
         }
     }
 
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompat.java b/src/com/android/launcher3/compat/LauncherAppsCompat.java
index 5858bc8..95e3ba9 100644
--- a/src/com/android/launcher3/compat/LauncherAppsCompat.java
+++ b/src/com/android/launcher3/compat/LauncherAppsCompat.java
@@ -53,7 +53,7 @@
     public static LauncherAppsCompat getInstance(Context context) {
         synchronized (sInstanceLock) {
             if (sInstance == null) {
-                if (Utilities.isLmpOrAbove()) {
+                if (Utilities.ATLEAST_LOLLIPOP) {
                     sInstance = new LauncherAppsCompatVL(context.getApplicationContext());
                 } else {
                     sInstance = new LauncherAppsCompatV16(context.getApplicationContext());
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatV16.java b/src/com/android/launcher3/compat/LauncherAppsCompatV16.java
index ac3d252..339c457 100644
--- a/src/com/android/launcher3/compat/LauncherAppsCompatV16.java
+++ b/src/com/android/launcher3/compat/LauncherAppsCompatV16.java
@@ -31,6 +31,7 @@
 import android.os.Bundle;
 import android.provider.Settings;
 
+import com.android.launcher3.Utilities;
 import com.android.launcher3.util.Thunk;
 
 import java.util.ArrayList;
@@ -188,7 +189,7 @@
                 // when moving a package or mounting/un-mounting external storage. Assume that
                 // it is a replacing operation.
                 final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING,
-                        Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT);
+                        !Utilities.ATLEAST_KITKAT);
                 String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
                 for (OnAppsChangedCallbackCompat callback : getCallbacks()) {
                     callback.onPackagesAvailable(packages, user, replacing);
diff --git a/src/com/android/launcher3/compat/PackageInstallerCompat.java b/src/com/android/launcher3/compat/PackageInstallerCompat.java
index c499083..ec5014d 100644
--- a/src/com/android/launcher3/compat/PackageInstallerCompat.java
+++ b/src/com/android/launcher3/compat/PackageInstallerCompat.java
@@ -34,7 +34,7 @@
     public static PackageInstallerCompat getInstance(Context context) {
         synchronized (sInstanceLock) {
             if (sInstance == null) {
-                if (Utilities.isLmpOrAbove()) {
+                if (Utilities.ATLEAST_LOLLIPOP) {
                     sInstance = new PackageInstallerCompatVL(context);
                 } else {
                     sInstance = new PackageInstallerCompatV16();
diff --git a/src/com/android/launcher3/compat/UserHandleCompat.java b/src/com/android/launcher3/compat/UserHandleCompat.java
index ab4b721..567022b 100644
--- a/src/com/android/launcher3/compat/UserHandleCompat.java
+++ b/src/com/android/launcher3/compat/UserHandleCompat.java
@@ -34,7 +34,7 @@
 
     @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
     public static UserHandleCompat myUserHandle() {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+        if (Utilities.ATLEAST_JB_MR1) {
             return new UserHandleCompat(android.os.Process.myUserHandle());
         } else {
             return new UserHandleCompat();
@@ -55,7 +55,7 @@
 
     @Override
     public String toString() {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+        if (Utilities.ATLEAST_JB_MR1) {
             return mUser.toString();
         } else {
             return "";
@@ -67,7 +67,7 @@
         if (!(other instanceof UserHandleCompat)) {
             return false;
         }
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+        if (Utilities.ATLEAST_JB_MR1) {
             return mUser.equals(((UserHandleCompat) other).mUser);
         } else {
             return true;
@@ -76,7 +76,7 @@
 
     @Override
     public int hashCode() {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+        if (Utilities.ATLEAST_JB_MR1) {
             return mUser.hashCode();
         } else {
             return 0;
@@ -89,7 +89,7 @@
      * profiles so this is a no-op.
      */
     public void addToIntent(Intent intent, String name) {
-        if (Utilities.isLmpOrAbove() && mUser != null) {
+        if (Utilities.ATLEAST_LOLLIPOP && mUser != null) {
             intent.putExtra(name, mUser);
         }
     }
diff --git a/src/com/android/launcher3/compat/UserManagerCompat.java b/src/com/android/launcher3/compat/UserManagerCompat.java
index 2ff1e7b..f708004 100644
--- a/src/com/android/launcher3/compat/UserManagerCompat.java
+++ b/src/com/android/launcher3/compat/UserManagerCompat.java
@@ -34,9 +34,9 @@
     public static UserManagerCompat getInstance(Context context) {
         synchronized (sInstanceLock) {
             if (sInstance == null) {
-                if (Utilities.isLmpOrAbove()) {
+                if (Utilities.ATLEAST_LOLLIPOP) {
                     sInstance = new UserManagerCompatVL(context.getApplicationContext());
-                } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+                } else if (Utilities.ATLEAST_JB_MR1) {
                     sInstance = new UserManagerCompatV17(context.getApplicationContext());
                 } else {
                     sInstance = new UserManagerCompatV16();
diff --git a/src/com/android/launcher3/compat/UserManagerCompatVL.java b/src/com/android/launcher3/compat/UserManagerCompatVL.java
index 4d404db..dc3ec3c 100644
--- a/src/com/android/launcher3/compat/UserManagerCompatVL.java
+++ b/src/com/android/launcher3/compat/UserManagerCompatVL.java
@@ -26,6 +26,7 @@
 import android.os.UserHandle;
 
 import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.util.LongArrayMap;
 
 import java.util.ArrayList;
@@ -100,7 +101,9 @@
 
     @Override
     public long getUserCreationTime(UserHandleCompat user) {
-        // TODO: Use system API once available.
+        if (Utilities.ATLEAST_MARSHMALLOW) {
+            return mUserManager.getUserCreationTime(user.getUser());
+        }
         SharedPreferences prefs = mContext.getSharedPreferences(
                 LauncherAppState.getSharedPreferencesKey(), Context.MODE_PRIVATE);
         String key = USER_CREATION_TIME_KEY + getSerialNumberForUser(user);
diff --git a/src/com/android/launcher3/util/ManagedProfileHeuristic.java b/src/com/android/launcher3/util/ManagedProfileHeuristic.java
index b37f447..74fc92a 100644
--- a/src/com/android/launcher3/util/ManagedProfileHeuristic.java
+++ b/src/com/android/launcher3/util/ManagedProfileHeuristic.java
@@ -68,7 +68,7 @@
     private static final long AUTO_ADD_TO_FOLDER_DURATION = 8 * 60 * 60 * 1000;
 
     public static ManagedProfileHeuristic get(Context context, UserHandleCompat user) {
-        if (Utilities.isLmpOrAbove() && !UserHandleCompat.myUserHandle().equals(user)) {
+        if (Utilities.ATLEAST_LOLLIPOP && !UserHandleCompat.myUserHandle().equals(user)) {
             return new ManagedProfileHeuristic(context, user);
         }
         return null;
@@ -296,7 +296,7 @@
      * Verifies that entries corresponding to {@param users} exist and removes all invalid entries.
      */
     public static void processAllUsers(List<UserHandleCompat> users, Context context) {
-        if (!Utilities.isLmpOrAbove()) {
+        if (!Utilities.ATLEAST_LOLLIPOP) {
             return;
         }
         UserManagerCompat userManager = UserManagerCompat.getInstance(context);
diff --git a/src/com/android/launcher3/util/UiThreadCircularReveal.java b/src/com/android/launcher3/util/UiThreadCircularReveal.java
index 3ca3aee..f2b5e5e 100644
--- a/src/com/android/launcher3/util/UiThreadCircularReveal.java
+++ b/src/com/android/launcher3/util/UiThreadCircularReveal.java
@@ -47,7 +47,7 @@
                 float progress = arg0.getAnimatedFraction();
                 outlineProvider.setProgress(progress);
                 revealView.invalidateOutline();
-                if (!Utilities.isLmpMR1OrAbove()) {
+                if (!Utilities.ATLEAST_LOLLIPOP_MR1) {
                     revealView.invalidate();
                 }
             }
diff --git a/src/com/android/launcher3/util/WallpaperUtils.java b/src/com/android/launcher3/util/WallpaperUtils.java
index 53b2acd..b9fccbc 100644
--- a/src/com/android/launcher3/util/WallpaperUtils.java
+++ b/src/com/android/launcher3/util/WallpaperUtils.java
@@ -24,6 +24,8 @@
 import android.os.Build;
 import android.view.WindowManager;
 
+import com.android.launcher3.Utilities;
+
 /**
  * Utility methods for wallpaper management.
  */
@@ -99,7 +101,7 @@
             int maxDim = Math.max(maxDims.x, maxDims.y);
             int minDim = Math.max(minDims.x, minDims.y);
 
-            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
+            if (Utilities.ATLEAST_JB_MR1) {
                 Point realSize = new Point();
                 windowManager.getDefaultDisplay().getRealSize(realSize);
                 maxDim = Math.max(realSize.x, realSize.y);
diff --git a/src/com/android/launcher3/widget/WidgetHostViewLoader.java b/src/com/android/launcher3/widget/WidgetHostViewLoader.java
index 30b3d58..461aebb 100644
--- a/src/com/android/launcher3/widget/WidgetHostViewLoader.java
+++ b/src/com/android/launcher3/widget/WidgetHostViewLoader.java
@@ -15,6 +15,7 @@
 import com.android.launcher3.DragSource;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 import com.android.launcher3.util.Thunk;
 
@@ -131,7 +132,7 @@
     public static Bundle getDefaultOptionsForWidget(Launcher launcher, PendingAddWidgetInfo info) {
         Bundle options = null;
         Rect rect = new Rect();
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+        if (Utilities.ATLEAST_JB_MR1) {
             AppWidgetResizeFrame.getWidgetSizeRanges(launcher, info.spanX, info.spanY, rect);
             Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(launcher,
                     info.componentName, null);
diff --git a/src/com/android/launcher3/widget/WidgetsListAdapter.java b/src/com/android/launcher3/widget/WidgetsListAdapter.java
index a54626a..f1cde29 100644
--- a/src/com/android/launcher3/widget/WidgetsListAdapter.java
+++ b/src/com/android/launcher3/widget/WidgetsListAdapter.java
@@ -169,7 +169,7 @@
 
         // if the end padding is 0, then container view (horizontal scroll view) doesn't respect
         // the end of the linear layout width + the start padding and doesn't allow scrolling.
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+        if (Utilities.ATLEAST_JB_MR1) {
             cellList.setPaddingRelative(mIndent, 0, 1, 0);
         } else {
             cellList.setPadding(mIndent, 0, 1, 0);
diff --git a/src/com/android/launcher3/widget/WidgetsRecyclerView.java b/src/com/android/launcher3/widget/WidgetsRecyclerView.java
index 61e63cd..e586dc2 100644
--- a/src/com/android/launcher3/widget/WidgetsRecyclerView.java
+++ b/src/com/android/launcher3/widget/WidgetsRecyclerView.java
@@ -102,7 +102,7 @@
 
         getCurScrollState(mScrollPosState);
         float pos = rowCount * touchFraction;
-        int availableScrollHeight = getAvailableScrollHeight(rowCount, mScrollPosState.rowHeight, 0);
+        int availableScrollHeight = getAvailableScrollHeight(rowCount, mScrollPosState.rowHeight);
         LinearLayoutManager layoutManager = ((LinearLayoutManager) getLayoutManager());
         layoutManager.scrollToPositionWithOffset(0, (int) -(availableScrollHeight * touchFraction));
 
@@ -115,29 +115,29 @@
      * Updates the bounds for the scrollbar.
      */
     @Override
-    public void onUpdateScrollbar() {
+    public void onUpdateScrollbar(int dy) {
         int rowCount = mWidgets.getPackageSize();
 
         // Skip early if, there are no items.
         if (rowCount == 0) {
-            mScrollbar.setScrollbarThumbOffset(-1, -1);
+            mScrollbar.setThumbOffset(-1, -1);
             return;
         }
 
         // Skip early if, there no child laid out in the container.
         getCurScrollState(mScrollPosState);
         if (mScrollPosState.rowIndex < 0) {
-            mScrollbar.setScrollbarThumbOffset(-1, -1);
+            mScrollbar.setThumbOffset(-1, -1);
             return;
         }
 
-        synchronizeScrollBarThumbOffsetToViewScroll(mScrollPosState, rowCount, 0);
+        synchronizeScrollBarThumbOffsetToViewScroll(mScrollPosState, rowCount);
     }
 
     /**
      * Returns the current scroll state.
      */
-    private void getCurScrollState(ScrollPositionState stateOut) {
+    protected void getCurScrollState(ScrollPositionState stateOut) {
         stateOut.rowIndex = -1;
         stateOut.rowTopOffset = -1;
         stateOut.rowHeight = -1;