Merge "Revert "Salt the package hash code using the device serial Bug: 62676166"" into ub-launcher3-dorval-polish
diff --git a/res/drawable-hdpi/all_apps_alpha_mask.png b/res/drawable-hdpi/all_apps_alpha_mask.png
new file mode 100755
index 0000000..01e9e56
--- /dev/null
+++ b/res/drawable-hdpi/all_apps_alpha_mask.png
Binary files differ
diff --git a/res/drawable-mdpi/all_apps_alpha_mask.png b/res/drawable-mdpi/all_apps_alpha_mask.png
new file mode 100755
index 0000000..f24e71d
--- /dev/null
+++ b/res/drawable-mdpi/all_apps_alpha_mask.png
Binary files differ
diff --git a/res/drawable-xhdpi/all_apps_alpha_mask.png b/res/drawable-xhdpi/all_apps_alpha_mask.png
new file mode 100755
index 0000000..f479433
--- /dev/null
+++ b/res/drawable-xhdpi/all_apps_alpha_mask.png
Binary files differ
diff --git a/res/drawable-xxhdpi/all_apps_alpha_mask.png b/res/drawable-xxhdpi/all_apps_alpha_mask.png
new file mode 100755
index 0000000..d5facd7
--- /dev/null
+++ b/res/drawable-xxhdpi/all_apps_alpha_mask.png
Binary files differ
diff --git a/res/drawable/all_apps_alpha_mask.png b/res/drawable-xxxhdpi/all_apps_alpha_mask.png
old mode 100644
new mode 100755
similarity index 100%
rename from res/drawable/all_apps_alpha_mask.png
rename to res/drawable-xxxhdpi/all_apps_alpha_mask.png
Binary files differ
diff --git a/res/values/config.xml b/res/values/config.xml
index db1a75d..00db4ef 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -124,7 +124,7 @@
     <item type="id" name="preview_image_id" />
 
 <!-- Popup items -->
-    <integer name="config_popupOpenCloseDuration">220</integer>
+    <integer name="config_popupOpenCloseDuration">150</integer>
     <integer name="config_popupArrowOpenDuration">80</integer>
     <integer name="config_removeNotificationViewDuration">300</integer>
 
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 6f2c897..aeb82b3 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -44,6 +44,7 @@
 import com.android.launcher3.IconCache.ItemInfoUpdateReceiver;
 import com.android.launcher3.badge.BadgeInfo;
 import com.android.launcher3.badge.BadgeRenderer;
+import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.folder.FolderIconPreviewVerifier;
 import com.android.launcher3.graphics.DrawableFactory;
 import com.android.launcher3.graphics.HolographicOutlineHelper;
@@ -438,6 +439,13 @@
         super.setTextColor(colors);
     }
 
+    public boolean shouldTextBeVisible() {
+        // Text should be visible everywhere but the hotseat.
+        Object tag = getParent() instanceof FolderIcon ? ((View) getParent()).getTag() : getTag();
+        ItemInfo info = tag instanceof ItemInfo ? (ItemInfo) tag : null;
+        return info == null || info.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT;
+    }
+
     public void setTextVisibility(boolean visible) {
         if (visible) {
             super.setTextColor(mTextColor);
@@ -459,7 +467,8 @@
      * @param fadeIn Whether the text should fade in or fade out.
      */
     public ObjectAnimator createTextAlphaAnimator(boolean fadeIn) {
-        return ObjectAnimator.ofInt(this, TEXT_ALPHA_PROPERTY, fadeIn ? Color.alpha(mTextColor) : 0);
+        int toAlpha = shouldTextBeVisible() && fadeIn ? Color.alpha(mTextColor) : 0;
+        return ObjectAnimator.ofInt(this, TEXT_ALPHA_PROPERTY, toAlpha);
     }
 
     @Override
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index d99a30a..42b64ea 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -606,7 +606,7 @@
         // Hotseat icons - remove text
         if (child instanceof BubbleTextView) {
             BubbleTextView bubbleChild = (BubbleTextView) child;
-            bubbleChild.setTextVisibility(mContainerType != HOTSEAT);
+            bubbleChild.setTextVisibility(bubbleChild.shouldTextBeVisible());
         }
 
         child.setScaleX(mChildScale);
diff --git a/src/com/android/launcher3/FirstFrameAnimatorHelper.java b/src/com/android/launcher3/FirstFrameAnimatorHelper.java
index a51ddd4..3cbc989 100644
--- a/src/com/android/launcher3/FirstFrameAnimatorHelper.java
+++ b/src/com/android/launcher3/FirstFrameAnimatorHelper.java
@@ -23,7 +23,6 @@
 import android.view.View;
 import android.view.ViewPropertyAnimator;
 import android.view.ViewTreeObserver;
-
 import com.android.launcher3.util.Thunk;
 
 /*
@@ -33,10 +32,11 @@
  */
 public class FirstFrameAnimatorHelper extends AnimatorListenerAdapter
     implements ValueAnimator.AnimatorUpdateListener {
+    private static final String TAG = "FirstFrameAnimatorHlpr";
     private static final boolean DEBUG = false;
     private static final int MAX_DELAY = 1000;
     private static final int IDEAL_FRAME_DURATION = 16;
-    private View mTarget;
+    private final View mTarget;
     private long mStartFrame;
     private long mStartTime = -1;
     private boolean mHandlingOnAnimationUpdate;
@@ -77,7 +77,7 @@
                     sGlobalFrameCounter++;
                     if (DEBUG) {
                         long newTime = System.currentTimeMillis();
-                        Log.d("FirstFrameAnimatorHelper", "TICK " + (newTime - mTime));
+                        Log.d(TAG, "TICK " + (newTime - mTime));
                         mTime = newTime;
                     }
                 }
@@ -139,7 +139,7 @@
 
     public void print(ValueAnimator animation) {
         float flatFraction = animation.getCurrentPlayTime() / (float) animation.getDuration();
-        Log.d("FirstFrameAnimatorHelper", sGlobalFrameCounter +
+        Log.d(TAG, sGlobalFrameCounter +
               "(" + (sGlobalFrameCounter - mStartFrame) + ") " + mTarget + " dirty? " +
               mTarget.isDirty() + " " + flatFraction + " " + this + " " + animation);
     }
diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java
index 21254ab..7959d40 100644
--- a/src/com/android/launcher3/FolderInfo.java
+++ b/src/com/android/launcher3/FolderInfo.java
@@ -45,6 +45,12 @@
      */
     public static final int FLAG_MULTI_PAGE_ANIMATION = 0x00000004;
 
+    /**
+     * The folder items ranks have been updated such that they appear unchanged with the new
+     * permutation display logic.
+     */
+    public static final int FLAG_ITEM_RANKS_UPDATED = 0x00000008;
+
     public int options;
 
     /**
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index 4813571..a4cd191 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -56,6 +56,7 @@
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dynamicui.ExtractionUtils;
+import com.android.launcher3.folder.FolderPagedView;
 import com.android.launcher3.graphics.IconShapeOverride;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.model.DbDowngradeHelper;
@@ -725,6 +726,87 @@
                         + "';l.profile=" + serial + ";', ';') where itemType = 0;";
                 db.execSQL(sql);
             }
+
+            updateExistingFoldersToMatchPrePermutationLayout(db);
+        }
+
+        /**
+         * We have changed the way we display items in Folders, but we want existing folders to
+         * appear the same.
+         *
+         * To make this change invisible to existing Folders, we need to update the ranks of the
+         * items such that, when displayed using the permutation, the order remains the same.
+         */
+        private void updateExistingFoldersToMatchPrePermutationLayout(SQLiteDatabase db) {
+            InvariantDeviceProfile idp = new InvariantDeviceProfile(mContext);
+            int maxCols = idp.numFolderColumns;
+            int maxRows = idp.numFolderRows;
+
+            try (SQLiteTransaction t = new SQLiteTransaction(db)) {
+                Cursor c = db.query(Favorites.TABLE_NAME,
+                        new String[] {Favorites._ID, Favorites.OPTIONS},
+                        "itemType=" + Favorites.ITEM_TYPE_FOLDER, null, null, null, null);
+
+                // For every Folder
+                while (c.moveToNext()) {
+                    final long folderId = c.getLong(c.getColumnIndexOrThrow(Favorites._ID));
+                    int options = c.getInt(c.getColumnIndexOrThrow(Favorites.OPTIONS));
+
+                    if ((options & FolderInfo.FLAG_ITEM_RANKS_UPDATED) != 0) {
+                        // The folder has already been updated.
+                        continue;
+                    }
+
+                    // For each item in the Folder
+                    Cursor c2 = db.query(Favorites.TABLE_NAME, new String[] {
+                                    Favorites._ID, Favorites.RANK, Favorites.CELLX, Favorites.CELLY,
+                                    Favorites.OPTIONS},
+                            "container=" + folderId, null, null, null, null);
+
+                    int numItemsInFolder = c2.getCount();
+
+                    // Calculate the grid size.
+                    int[] gridXY = new int[2];
+                    FolderPagedView.calculateGridSize(numItemsInFolder, 0, 0, maxCols, maxRows,
+                            maxCols * maxRows, gridXY);
+                    int gridX = gridXY[0];
+                    int gridY = gridXY[1];
+                    int maxItemsPerPage = gridX * gridY;
+
+                    // We create a mapping from the permutation to the original rank (ie. the
+                    // inverse permutation). This is what we'll use to set the folder items so that
+                    // they appear in their original order.
+                    int[] inversion = new int[numItemsInFolder];
+                    for (int i = 0; i < numItemsInFolder; ++i) {
+                        int permutation = FolderPagedView.getReadingOrderPosForRank(i,
+                                maxItemsPerPage, gridX, null);
+                        inversion[permutation] = i;
+                    }
+
+                    // Now we update the ranks of the folder items. Note that cellX/cellY stay the
+                    // same, due to the permutation.
+                    for (int i = 0; i < numItemsInFolder && c2.moveToNext(); ++i) {
+                        final int rank = c2.getInt(c2.getColumnIndexOrThrow(Favorites.RANK));
+
+                        SQLiteStatement updateItem = db.compileStatement(
+                                "UPDATE favorites SET rank=" + inversion[rank] + " WHERE _id=?");
+                        updateItem.bindLong(1, c2.getInt(c2.getColumnIndexOrThrow(Favorites._ID)));
+                        updateItem.executeUpdateDelete();
+                    }
+                    c2.close();
+
+                    // Mark the folder as having been updated.
+                    options |= FolderInfo.FLAG_ITEM_RANKS_UPDATED;
+                    SQLiteStatement updateFolder = db.compileStatement(
+                            "UPDATE favorites SET options=" + options + " WHERE _id=?");
+                    updateFolder.bindLong(1, folderId);
+                    updateFolder.executeUpdateDelete();
+                }
+                c.close();
+                t.commit();
+            } catch (SQLException ex) {
+                Log.w(TAG, "Error updating folder items to match permutation.", ex);
+            }
         }
 
         @Override
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 189b935..499eb45 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -423,8 +423,9 @@
 
             // We only show the spring animation when at the top or bottom, so we wait until the
             // first or last row is visible to ensure that all animations run in sync.
-            if ((first == 0 && dy < 0) || (last == mAdapter.getItemCount() - 1 && dy > 0)) {
-                mSpringAnimationHandler.animateToFinalPosition(0);
+            boolean scrollUp = dy < 0;
+            if ((first == 0 && scrollUp) || (last == mAdapter.getItemCount() - 1 && dy > 0)) {
+                mSpringAnimationHandler.animateToFinalPosition(0, scrollUp ? 1 : -1);
             }
         }
 
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index a2bd43d..34421bd 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -511,7 +511,9 @@
                 if (FeatureFlags.LAUNCHER3_PHYSICS) {
                     // We calculate our own velocity to give the springs the desired effect.
                     velocity = y / getDampedOverScroll(getHeight()) * MAX_RELEASE_VELOCITY;
-                    mSpringAnimationHandler.animateToPositionWithVelocity(0, -velocity);
+                    // We want to negate the velocity because we are moving to 0 from -1 due to the
+                    // downward motion. (y-axis -1 is above 0).
+                    mSpringAnimationHandler.animateToPositionWithVelocity(0, -1, -velocity);
                 }
 
                 ObjectAnimator.ofFloat(AllAppsRecyclerView.this,
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 4d112c6..0859e06 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -228,7 +228,8 @@
                 }
                 mLauncher.showAppsView(true /* animated */, false /* updatePredictedApps */);
                 if (hasSpringAnimationHandler()) {
-                    mSpringAnimationHandler.animateToFinalPosition(0);
+                    // The icons are moving upwards, so we go to 0 from 1. (y-axis 1 is below 0.)
+                    mSpringAnimationHandler.animateToFinalPosition(0 /* pos */, 1 /* startValue */);
                 }
             } else {
                 calculateDuration(velocity, Math.abs(mShiftRange - mAppsView.getTranslationY()));
diff --git a/src/com/android/launcher3/anim/SpringAnimationHandler.java b/src/com/android/launcher3/anim/SpringAnimationHandler.java
index 1efc4e4..3e58adc 100644
--- a/src/com/android/launcher3/anim/SpringAnimationHandler.java
+++ b/src/com/android/launcher3/anim/SpringAnimationHandler.java
@@ -111,16 +111,32 @@
         mShouldComputeVelocity = true;
     }
 
-    public void animateToFinalPosition(float position) {
-        if (DEBUG) Log.d(TAG, "animateToFinalPosition#computeVelocity=" + mShouldComputeVelocity);
+    public void animateToFinalPosition(float position, int startValue) {
+        animateToFinalPosition(position, startValue, mShouldComputeVelocity);
+    }
+
+    /**
+     * @param position The final animation position.
+     * @param startValue < 0 if scrolling from start to end; > 0 if scrolling from end to start
+     *                   The magnitude of the number changes how the spring will move.
+     * @param setVelocity If true, we set the velocity to {@link #mCurrentVelocity} before
+     *                    starting the animation.
+     */
+    private void animateToFinalPosition(float position, int startValue, boolean setVelocity) {
+        if (DEBUG) {
+            Log.d(TAG, "animateToFinalPosition#position=" + position + ", startValue=" + startValue);
+        }
 
         if (mShouldComputeVelocity) {
-            computeVelocity();
-            setStartVelocity(mCurrentVelocity);
+            mCurrentVelocity = computeVelocity();
         }
 
         int size = mAnimations.size();
         for (int i = 0; i < size; ++i) {
+            mAnimations.get(i).setStartValue(startValue);
+            if (setVelocity) {
+                mAnimations.get(i).setStartVelocity(mCurrentVelocity);
+            }
             mAnimations.get(i).animateToFinalPosition(position);
         }
 
@@ -128,15 +144,18 @@
     }
 
     /**
-     * Similar to {@link #animateToFinalPosition(float)}, but used in cases where we want to
+     * Similar to {@link #animateToFinalPosition(float, int)}, but used in cases where we want to
      * manually set the velocity.
      */
-    public void animateToPositionWithVelocity(float position, float velocity) {
-        if (DEBUG) Log.d(TAG, "animateToPosition#velocity=" + velocity);
+    public void animateToPositionWithVelocity(float position, int startValue, float velocity) {
+        if (DEBUG) {
+            Log.d(TAG, "animateToPosition#pos=" + position + ", start=" + startValue
+                    + ", velocity=" + velocity);
+        }
 
-        setStartVelocity(velocity);
+        mCurrentVelocity = velocity;
         mShouldComputeVelocity = false;
-        animateToFinalPosition(position);
+        animateToFinalPosition(position, startValue, true);
     }
 
 
@@ -163,27 +182,20 @@
             mVelocityTracker = null;
         }
         mCurrentVelocity = 0;
+        mShouldComputeVelocity = false;
     }
 
-    private void setStartVelocity(float velocity) {
-        if (DEBUG) Log.d(TAG, "setStartVelocity=" + velocity);
 
-        int size = mAnimations.size();
-        for (int i = 0; i < size; ++i) {
-            mAnimations.get(i).setStartVelocity(velocity);
-        }
-    }
-
-    private void computeVelocity() {
+    private float computeVelocity() {
         getVelocityTracker().computeCurrentVelocity(1000 /* millis */);
 
-        mCurrentVelocity = isVerticalDirection()
+        float velocity = isVerticalDirection()
                 ? getVelocityTracker().getYVelocity()
                 : getVelocityTracker().getXVelocity();
-        mCurrentVelocity *= VELOCITY_DAMPING_FACTOR;
-        mShouldComputeVelocity = false;
+        velocity *= VELOCITY_DAMPING_FACTOR;
 
-        if (DEBUG) Log.d(TAG, "computeVelocity=" + mCurrentVelocity);
+        if (DEBUG) Log.d(TAG, "computeVelocity=" + velocity);
+        return velocity;
     }
 
     private boolean isVerticalDirection() {
@@ -221,7 +233,6 @@
      */
     public static SpringAnimation forView(View view, FloatPropertyCompat property, float finalPos) {
         SpringAnimation spring = new SpringAnimation(view, property, finalPos);
-        spring.setStartValue(1f);
         spring.setSpring(new SpringForce(finalPos));
         return spring;
     }
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index 21631fa..bc26fbe 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -755,6 +755,10 @@
         return mMaxItemsPerPage;
     }
 
+    public int getReadingOrderPosForRank(int rank) {
+        return getReadingOrderPosForRank(rank, mMaxItemsPerPage, mGridCountX, sTmpArray);
+    }
+
     /**
      * Returns the reading order position for a given rank.
      *
@@ -763,34 +767,44 @@
      *
      *     R0 R1 R4
      *     R2 R3 R5
+     *
+     * @param outXY If notnull, we also return the cell X/Y position.
      */
-    public int getReadingOrderPosForRank(int rank) {
-        if (rank >= mMaxItemsPerPage) {
+    public static int getReadingOrderPosForRank(int rank, int maxItemsPerPage, int gridX,
+            int[] outXY) {
+        outXY = outXY == null ? sTmpArray : outXY;
+        getCellXYPositionForRank(rank, maxItemsPerPage, gridX, outXY);
+
+        if (rank >= maxItemsPerPage) {
             return rank;
         }
 
-        getCellXYPositionForRank(rank, sTmpArray);
-        return sTmpArray[0] + (mGridCountX * sTmpArray[1]);
+        return outXY[0] + (gridX * outXY[1]);
+    }
+
+    public void getCellXYPositionForRank(int rank, int[] outXY) {
+        getCellXYPositionForRank(rank, mMaxItemsPerPage, mGridCountX, outXY);
     }
 
     /**
      * Returns the cell XY position for a Folder item with the given rank.
      */
-    public void getCellXYPositionForRank(int rank, int[] outXY) {
-        boolean onFirstPage = rank < mMaxItemsPerPage;
+    public static void getCellXYPositionForRank(int rank, int maxItemsPerPage, int gridX,
+            int[] outXY) {
+        boolean onFirstPage = rank < maxItemsPerPage;
 
-        if (onFirstPage && mGridCountX == 3) {
+        if (onFirstPage && gridX == 3) {
             outXY[0] = FolderPermutation.THREE_COLS[rank][0];
             outXY[1] = FolderPermutation.THREE_COLS[rank][1];
-        } else if (onFirstPage && mGridCountX == 4) {
+        } else if (onFirstPage && gridX == 4) {
             outXY[0] = FolderPermutation.FOUR_COLS[rank][0];
             outXY[1] = FolderPermutation.FOUR_COLS[rank][1];
-        } else if (onFirstPage && mGridCountX == 5) {
+        } else if (onFirstPage && gridX == 5) {
             outXY[0] = FolderPermutation.FIVE_COLS[rank][0];
             outXY[1] = FolderPermutation.FIVE_COLS[rank][1];
         } else {
-            outXY[0] = (rank % mMaxItemsPerPage) % mGridCountX;
-            outXY[1] = (rank % mMaxItemsPerPage) / mGridCountX;
+            outXY[0] = (rank % maxItemsPerPage) % gridX;
+            outXY[1] = (rank % maxItemsPerPage) / gridX;
         }
     }
 
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index 1a2c04d..bc7da9b 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -184,9 +184,13 @@
                 icon = LauncherIcons.createIconBitmap(
                         BitmapFactory.decodeByteArray(data, 0, data.length), mContext);
             } catch (Exception e) {
+                Log.e(TAG, "Failed to load icon for info " + info, e);
                 return null;
             }
         }
+        if (icon == null) {
+            Log.e(TAG, "Failed to load icon for info " + info);
+        }
         return icon;
     }
 
diff --git a/src/com/android/launcher3/notification/NotificationListener.java b/src/com/android/launcher3/notification/NotificationListener.java
index baaa66a..8121fd5 100644
--- a/src/com/android/launcher3/notification/NotificationListener.java
+++ b/src/com/android/launcher3/notification/NotificationListener.java
@@ -127,8 +127,9 @@
         }
         sNotificationsChangedListener = listener;
 
-        if (sNotificationListenerInstance != null) {
-            sNotificationListenerInstance.onNotificationFullRefresh();
+        NotificationListener notificationListener = getInstanceIfConnected();
+        if (notificationListener != null) {
+            notificationListener.onNotificationFullRefresh();
         }
     }
 
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 425a08d..c6ae0d2 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -20,6 +20,7 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
 import android.annotation.TargetApi;
 import android.content.Context;
@@ -53,7 +54,6 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.LauncherModel;
-import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
@@ -354,6 +354,8 @@
 
         final AnimatorSet openAnim = LauncherAnimUtils.createAnimatorSet();
         final Resources res = getResources();
+        final long revealDuration = (long) res.getInteger(R.integer.config_popupOpenCloseDuration);
+        final TimeInterpolator revealInterpolator = new AccelerateDecelerateInterpolator();
 
         // Rectangular reveal.
         int itemsTotalHeight = 0;
@@ -367,8 +369,13 @@
         mEndRect.set(0, top, getMeasuredWidth(), top + itemsTotalHeight);
         final ValueAnimator revealAnim = new RoundedRectRevealOutlineProvider
                 (radius, radius, mStartRect, mEndRect).createRevealAnimator(this, false);
-        revealAnim.setDuration((long) res.getInteger(R.integer.config_popupOpenCloseDuration));
-        revealAnim.setInterpolator(new AccelerateDecelerateInterpolator());
+        revealAnim.setDuration(revealDuration);
+        revealAnim.setInterpolator(revealInterpolator);
+
+        Animator fadeIn = ObjectAnimator.ofFloat(this, ALPHA, 0, 1);
+        fadeIn.setDuration(revealDuration);
+        fadeIn.setInterpolator(revealInterpolator);
+        openAnim.play(fadeIn);
 
         // Animate the arrow.
         mArrow.setScaleX(0);
@@ -852,10 +859,8 @@
 
         final AnimatorSet closeAnim = LauncherAnimUtils.createAnimatorSet();
         final Resources res = getResources();
-
-        // Animate the arrow.
-        Animator arrowScale = createArrowScaleAnim(0).setDuration(res.getInteger(
-                R.integer.config_popupArrowOpenDuration));
+        final long revealDuration = (long) res.getInteger(R.integer.config_popupOpenCloseDuration);
+        final TimeInterpolator revealInterpolator = new AccelerateDecelerateInterpolator();
 
         // Rectangular reveal (reversed).
         int itemsTotalHeight = 0;
@@ -871,9 +876,14 @@
         }
         final ValueAnimator revealAnim = new RoundedRectRevealOutlineProvider(
                 radius, radius, mStartRect, mEndRect).createRevealAnimator(this, true);
-        long revealDuration = (long) res.getInteger(R.integer.config_popupOpenCloseDuration);
         revealAnim.setDuration(revealDuration);
-        revealAnim.setInterpolator(new AccelerateDecelerateInterpolator());
+        revealAnim.setInterpolator(revealInterpolator);
+        closeAnim.play(revealAnim);
+
+        Animator fadeOut = ObjectAnimator.ofFloat(this, ALPHA, 0);
+        fadeOut.setDuration(revealDuration);
+        fadeOut.setInterpolator(revealInterpolator);
+        closeAnim.play(fadeOut);
 
         // Animate original icon's text back in.
         Animator fadeText = mOriginalIcon.createTextAlphaAnimator(true /* fadeIn */);
@@ -892,7 +902,6 @@
             }
         });
         mOpenCloseAnimator = closeAnim;
-        closeAnim.playSequentially(arrowScale, revealAnim);
         closeAnim.start();
         mOriginalIcon.forceHideBadge(false);
     }
@@ -907,9 +916,7 @@
         }
         mIsOpen = false;
         mDeferContainerRemoval = false;
-        boolean isInHotseat = ((ItemInfo) mOriginalIcon.getTag()).container
-                == LauncherSettings.Favorites.CONTAINER_HOTSEAT;
-        mOriginalIcon.setTextVisibility(!isInHotseat);
+        mOriginalIcon.setTextVisibility(mOriginalIcon.shouldTextBeVisible());
         mOriginalIcon.forceHideBadge(false);
         mLauncher.getDragController().removeDragListener(this);
         mLauncher.getDragLayer().removeView(this);
diff --git a/src/com/android/launcher3/widget/WidgetsBottomSheet.java b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
index 8522169..a754375 100644
--- a/src/com/android/launcher3/widget/WidgetsBottomSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
@@ -44,6 +44,7 @@
 import com.android.launcher3.anim.PropertyListBuilder;
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.graphics.GradientView;
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.util.PackageUserKey;
@@ -71,6 +72,7 @@
     private VerticalPullDetector.ScrollInterpolator mScrollInterpolator;
     private Rect mInsets;
     private VerticalPullDetector mVerticalPullDetector;
+    private GradientView mGradientBackground;
 
     public WidgetsBottomSheet(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
@@ -87,6 +89,7 @@
         mInsets = new Rect();
         mVerticalPullDetector = new VerticalPullDetector(context);
         mVerticalPullDetector.setListener(this);
+        mGradientBackground = (GradientView) mLauncher.findViewById(R.id.gradient_bg);
     }
 
     @Override
@@ -270,6 +273,13 @@
     }
 
     @Override
+    public void setTranslationY(float translationY) {
+        super.setTranslationY(translationY);
+        if (mGradientBackground == null) return;
+        mGradientBackground.setProgress((mTranslationYClosed - translationY) / mTranslationYRange);
+    }
+
+    @Override
     public void onDragEnd(float velocity, boolean fling) {
         if ((fling && velocity > 0) || getTranslationY() > (mTranslationYRange) / 2) {
             mScrollInterpolator.setVelocityAtZero(velocity);