Introduced the Notification shelf

Started the implementation of a new notification
scrolling model that dynamically transforms notifications
into a new icon shelf.

Test: Add notifications, scroll!
Bug: 32437839
Change-Id: Ic28cc1c82f2cda2ffa7a312648903b5bc2408820
diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java
index c46acae..ed4188e 100644
--- a/core/java/android/view/NotificationHeaderView.java
+++ b/core/java/android/view/NotificationHeaderView.java
@@ -311,6 +311,10 @@
         return mProfileBadge;
     }
 
+    public View getIcon() {
+        return mIcon;
+    }
+
     public class HeaderTouchListener implements View.OnTouchListener {
 
         private final ArrayList<Rect> mTouchRects = new ArrayList<>();
diff --git a/packages/SystemUI/res/layout/notification_icon_area.xml b/packages/SystemUI/res/layout/notification_icon_area.xml
index c5b4e84..6732e6c 100644
--- a/packages/SystemUI/res/layout/notification_icon_area.xml
+++ b/packages/SystemUI/res/layout/notification_icon_area.xml
@@ -19,13 +19,7 @@
     android:id="@+id/notification_icon_area_inner"
     android:layout_width="match_parent"
     android:layout_height="match_parent" >
-    <com.android.systemui.statusbar.StatusBarIconView
-        android:id="@+id/moreIcon"
-        android:layout_width="@dimen/status_bar_icon_size"
-        android:layout_height="match_parent"
-        android:src="@drawable/stat_notify_more"
-        android:visibility="gone" />
-    <com.android.systemui.statusbar.phone.IconMerger
+    <com.android.systemui.statusbar.phone.NotificationIconContainer
         android:id="@+id/notificationIcons"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
diff --git a/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml b/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml
deleted file mode 100644
index 7df6bc6..0000000
--- a/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml
+++ /dev/null
@@ -1,74 +0,0 @@
-<!--
-  ~ Copyright (C) 2014 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-
-<!-- Extends FrameLayout -->
-<com.android.systemui.statusbar.NotificationOverflowContainer
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="@dimen/notification_summary_height"
-    android:focusable="true"
-    android:clickable="true"
-    >
-
-    <com.android.systemui.statusbar.NotificationBackgroundView android:id="@+id/backgroundNormal"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        />
-    <com.android.systemui.statusbar.NotificationBackgroundView android:id="@+id/backgroundDimmed"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        />
-
-    <com.android.keyguard.AlphaOptimizedLinearLayout
-        android:id="@+id/content"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent">
-        <TextView
-            android:id="@+id/more_text"
-            android:layout_width="32dp"
-            android:layout_height="32dp"
-            android:layout_marginStart="16dp"
-            android:layout_marginEnd="12dp"
-            android:layout_gravity="center_vertical"
-            android:background="@drawable/keyguard_overflow_number_background"
-            android:gravity="center"
-            android:textColor="#ff686868"
-            android:textStyle="bold"
-            android:textSize="14dp"
-            />
-        <com.android.systemui.statusbar.StatusBarIconView
-            android:id="@+id/more_icon_overflow"
-            android:layout_width="@dimen/status_bar_icon_size"
-            android:layout_height="match_parent"
-            android:src="@drawable/stat_notify_more"
-            android:tint="@color/keyguard_overflow_content_color"
-            android:visibility="gone"
-            />
-        <com.android.systemui.statusbar.NotificationOverflowIconsView
-            android:id="@+id/overflow_icons_view"
-            android:layout_gravity="center_vertical"
-            android:layout_marginEnd="8dp"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            />
-    </com.android.keyguard.AlphaOptimizedLinearLayout>
-
-    <com.android.systemui.statusbar.notification.FakeShadowView
-        android:id="@+id/fake_shadow"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent" />
-
-</com.android.systemui.statusbar.NotificationOverflowContainer>
diff --git a/packages/SystemUI/res/layout/status_bar_notification_shelf.xml b/packages/SystemUI/res/layout/status_bar_notification_shelf.xml
new file mode 100644
index 0000000..fde61eb
--- /dev/null
+++ b/packages/SystemUI/res/layout/status_bar_notification_shelf.xml
@@ -0,0 +1,47 @@
+<!--
+  ~ Copyright (C) 2016 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<!-- Extends FrameLayout -->
+<com.android.systemui.statusbar.NotificationShelf
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="@dimen/notification_shelf_height"
+    android:focusable="true"
+    android:clickable="true"
+    >
+
+    <com.android.systemui.statusbar.NotificationBackgroundView android:id="@+id/backgroundNormal"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        />
+    <com.android.systemui.statusbar.NotificationBackgroundView android:id="@+id/backgroundDimmed"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        />
+    <com.android.systemui.statusbar.phone.NotificationIconContainer
+        android:id="@+id/content"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_marginStart="13dp"
+        android:layout_marginEnd="13dp"
+        android:gravity="center_vertical" />
+
+    <com.android.systemui.statusbar.notification.FakeShadowView
+        android:id="@+id/fake_shadow"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+</com.android.systemui.statusbar.NotificationShelf>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 12f7881..943c67c 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -81,8 +81,12 @@
     <!-- Height of a heads up notification in the status bar -->
     <dimen name="notification_max_heads_up_height">148dp</dimen>
 
-    <!-- Height of a the summary ("more card") notification on keyguard. -->
-    <dimen name="notification_summary_height">44dp</dimen>
+    <!-- Height of a the shelf with the notification icons -->
+    <dimen name="notification_shelf_height">32dp</dimen>
+
+    <!-- The padding of a notification icon on top to the start of the notification. Used for custom
+         views where the distance can't be measured -->
+    <dimen name="notification_icon_appear_padding">15dp</dimen>
 
     <!-- Minimum layouted height of a notification in the statusbar-->
     <dimen name="min_notification_layout_height">48dp</dimen>
@@ -94,7 +98,7 @@
     <dimen name="notification_gear_padding">20dp</dimen>
 
     <!-- size at which Notification icons will be drawn in the status bar -->
-    <dimen name="status_bar_icon_drawing_size">17dip</dimen>
+    <dimen name="status_bar_icon_drawing_size">17dp</dimen>
 
     <!-- opacity at which Notification icons will be drawn in the status bar -->
     <item type="dimen" name="status_bar_icon_drawing_alpha">90%</item>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
index bc46548..0616db5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
@@ -41,7 +41,7 @@
 import com.android.systemui.statusbar.stack.StackStateAnimator;
 
 /**
- * Base class for both {@link ExpandableNotificationRow} and {@link NotificationOverflowContainer}
+ * Base class for both {@link ExpandableNotificationRow} and {@link NotificationShelf}
  * to implement dimming/activating on Keyguard for the double-tap gesture
  */
 public abstract class ActivatableNotificationView extends ExpandableOutlineView {
@@ -131,7 +131,7 @@
     private final int mLegacyColor;
     private final int mNormalColor;
     private final int mLowPriorityColor;
-    private boolean mIsBelowSpeedBump;
+    private boolean mIsBelowShelf;
     private FalsingManager mFalsingManager;
     private boolean mTrackTouch;
 
@@ -443,10 +443,10 @@
     }
 
     @Override
-    public void setBelowSpeedBump(boolean below) {
-        super.setBelowSpeedBump(below);
-        if (below != mIsBelowSpeedBump) {
-            mIsBelowSpeedBump = below;
+    public void setBelowShelf(boolean below) {
+        super.setBelowShelf(below);
+        if (below != mIsBelowShelf) {
+            mIsBelowShelf = below;
             updateBackgroundTint();
         }
     }
@@ -849,7 +849,7 @@
             return mBgTint;
         } else if (mShowingLegacyBackground) {
             return mLegacyColor;
-        } else if (mIsBelowSpeedBump) {
+        } else if (mIsBelowShelf) {
             return mLowPriorityColor;
         } else {
             return mNormalColor;
@@ -861,7 +861,7 @@
             return mTintedRippleColor;
         } else if (mShowingLegacyBackground) {
             return mTintedRippleColor;
-        } else if (mIsBelowSpeedBump) {
+        } else if (mIsBelowShelf) {
             return mLowPriorityRippleColor;
         } else {
             return mNormalRippleColor;
@@ -907,7 +907,7 @@
         setTintColor(0);
         resetBackgroundAlpha();
         setShowingLegacyBackground(false);
-        setBelowSpeedBump(false);
+        setBelowShelf(false);
     }
 
     public boolean hasSameBgColor(ActivatableNotificationView otherView) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 19e511cf..4649d50 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -42,7 +42,6 @@
 import android.database.ContentObserver;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
 import android.os.AsyncTask;
 import android.os.Build;
 import android.os.Bundle;
@@ -258,7 +257,7 @@
     protected boolean mShowLockscreenNotifications;
     protected boolean mAllowLockscreenRemoteInput;
 
-    protected NotificationOverflowContainer mKeyguardIconOverflowContainer;
+    protected NotificationShelf mNotificationShelf;
     protected DismissView mDismissView;
     protected EmptyShadeView mEmptyShadeView;
 
@@ -1025,9 +1024,7 @@
             }
         }
 
-        if (entry.icon != null) {
-            entry.icon.setTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
-        }
+        entry.setIconTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
     }
 
     public boolean isMediaNotification(NotificationData.Entry entry) {
@@ -2160,13 +2157,14 @@
         if (DEBUG) {
             Log.d(TAG, "createNotificationViews(notification=" + sbn);
         }
-        final StatusBarIconView iconView = createIcon(sbn);
-        if (iconView == null) {
-            return null;
+        NotificationData.Entry entry = new NotificationData.Entry(sbn);
+        try {
+            entry.createIcons(mContext, sbn);
+        } catch (NotificationData.IconException exception) {
+            handleNotificationError(sbn, exception.getMessage());
         }
 
         // Construct the expanded view.
-        NotificationData.Entry entry = new NotificationData.Entry(sbn, iconView);
         if (!inflateViews(entry, mStackScroller)) {
             handleNotificationError(sbn, "Couldn't expand RemoteViews for: " + sbn);
             return null;
@@ -2174,33 +2172,6 @@
         return entry;
     }
 
-    public StatusBarIconView createIcon(StatusBarNotification sbn) {
-        // Construct the icon.
-        Notification n = sbn.getNotification();
-        final StatusBarIconView iconView = new StatusBarIconView(mContext,
-                sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), n);
-        iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
-
-        final Icon smallIcon = n.getSmallIcon();
-        if (smallIcon == null) {
-            handleNotificationError(sbn,
-                    "No small icon in notification from " + sbn.getPackageName());
-            return null;
-        }
-        final StatusBarIcon ic = new StatusBarIcon(
-                sbn.getUser(),
-                sbn.getPackageName(),
-                smallIcon,
-                n.iconLevel,
-                n.number,
-                StatusBarIconView.contentDescForNotification(mContext, n));
-        if (!iconView.set(ic)) {
-            handleNotificationError(sbn, "Couldn't create icon: " + ic);
-            return null;
-        }
-        return iconView;
-    }
-
     protected void addNotificationViews(Entry entry, RankingMap ranking) {
         if (entry == null) {
             return;
@@ -2220,8 +2191,6 @@
      * Updates expanded, dimmed and locked states of notification rows.
      */
     protected void updateRowStates() {
-        mKeyguardIconOverflowContainer.getIconsView().removeAllViews();
-
         ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
         final int N = activeNotifications.size();
 
@@ -2252,9 +2221,6 @@
                     || (onKeyguard && !childWithVisibleSummary
                             && (visibleNotifications >= maxNotifications || !showOnKeyguard))) {
                 entry.row.setVisibility(View.GONE);
-                if (onKeyguard && showOnKeyguard && !childNotification && !suppressedSummary) {
-                    mKeyguardIconOverflowContainer.getIconsView().addNotification(entry);
-                }
             } else {
                 boolean wasGone = entry.row.getVisibility() == View.GONE;
                 entry.row.setVisibility(View.VISIBLE);
@@ -2269,12 +2235,9 @@
             }
         }
 
-        mStackScroller.updateOverflowContainerVisibility(onKeyguard
-                && mKeyguardIconOverflowContainer.getIconsView().getChildCount() > 0);
-
         mStackScroller.changeViewPosition(mDismissView, mStackScroller.getChildCount() - 1);
         mStackScroller.changeViewPosition(mEmptyShadeView, mStackScroller.getChildCount() - 2);
-        mStackScroller.changeViewPosition(mKeyguardIconOverflowContainer,
+        mStackScroller.changeViewPosition(mNotificationShelf,
                 mStackScroller.getChildCount() - 3);
     }
 
@@ -2367,48 +2330,28 @@
         mGroupManager.onEntryUpdated(entry, oldNotification);
 
         boolean updateSuccessful = false;
-        if (applyInPlace) {
-            if (DEBUG) Log.d(TAG, "reusing notification for key: " + key);
-            try {
-                if (entry.icon != null) {
-                    // Update the icon
-                    final StatusBarIcon ic = new StatusBarIcon(
-                            notification.getUser(),
-                            notification.getPackageName(),
-                            n.getSmallIcon(),
-                            n.iconLevel,
-                            n.number,
-                            StatusBarIconView.contentDescForNotification(mContext, n));
-                    entry.icon.setNotification(n);
-                    if (!entry.icon.set(ic)) {
-                        handleNotificationError(notification, "Couldn't update icon: " + ic);
-                        return;
-                    }
+        try {
+            if (applyInPlace) {
+                if (DEBUG) Log.d(TAG, "reusing notification for key: " + key);
+                try {
+                    entry.updateIcons(mContext, n);
+                    updateNotificationViews(entry, notification);
+                    updateSuccessful = true;
+                } catch (RuntimeException e) {
+                    // It failed to apply cleanly.
+                    Log.w(TAG, "Couldn't reapply views for package " +
+                            notification.getPackageName(), e);
                 }
-                updateNotificationViews(entry, notification);
-                updateSuccessful = true;
             }
-            catch (RuntimeException e) {
-                // It failed to apply cleanly.
-                Log.w(TAG, "Couldn't reapply views for package " +
-                        notification.getPackageName(), e);
+            if (!updateSuccessful) {
+                entry.updateIcons(mContext, n);
+                if (!inflateViews(entry, mStackScroller)) {
+                    handleNotificationError(notification, "Couldn't update remote views for: "
+                            + notification);
+                }
             }
-        }
-        if (!updateSuccessful) {
-            if (DEBUG) Log.d(TAG, "not reusing notification for key: " + key);
-            final StatusBarIcon ic = new StatusBarIcon(
-                    notification.getUser(),
-                    notification.getPackageName(),
-                    n.getSmallIcon(),
-                    n.iconLevel,
-                    n.number,
-                    StatusBarIconView.contentDescForNotification(mContext, n));
-            entry.icon.setNotification(n);
-            entry.icon.set(ic);
-            if (!inflateViews(entry, mStackScroller)) {
-                handleNotificationError(notification, "Couldn't update remote views for: "
-                        + notification);
-            }
+        } catch (NotificationData.IconException e) {
+            handleNotificationError(notification, e.getMessage());
         }
         updateHeadsUp(key, entry, shouldPeek, alertAgain);
         updateNotifications();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index c9a5fdf..6e690f8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -318,6 +318,10 @@
         return mStatusBarNotification;
     }
 
+    public NotificationData.Entry getEntry() {
+        return mEntry;
+    }
+
     public boolean isHeadsUp() {
         return mIsHeadsUp;
     }
@@ -785,6 +789,26 @@
         mVetoButton.setOnClickListener(listener);
     }
 
+    public View getNotificationIcon() {
+        NotificationHeaderView notificationHeader = getNotificationHeader();
+        if (notificationHeader != null) {
+            return notificationHeader.getIcon();
+        }
+        return null;
+    }
+
+    /**
+     * @return whether the notification is currently showing a view with an icon.
+     */
+    public boolean isShowingIcon() {
+        if (mIsSummaryWithChildren) {
+            return true;
+        }
+        NotificationContentView showingLayout = getShowingLayout();
+        NotificationHeaderView notificationHeader = showingLayout.getVisibleNotificationHeader();
+        return notificationHeader != null;
+    }
+
     public interface ExpansionLogger {
         public void logNotificationExpansion(String key, boolean userAction, boolean expanded);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
index b97d345..29a0e6b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
@@ -263,7 +263,11 @@
 
     public abstract void performAddAnimation(long delay, long duration);
 
-    public void setBelowSpeedBump(boolean below) {
+    /**
+     * Set the notification appearance to be below the shelf.
+     * @param below true if it is below.
+     */
+    public void setBelowShelf(boolean below) {
     }
 
     /**
@@ -445,6 +449,15 @@
     }
 
     /**
+     * @return whether the current view doesn't add height to the overall content. This means that
+     * if it is added to a list of items, it's content will still have the same height.
+     * An example is the notification shelf, that is always placed on top of another view.
+     */
+    public boolean hasNoContentHeight() {
+        return false;
+    }
+
+    /**
      * A listener notifying when {@link #getActualHeight} changes.
      */
     public interface OnHeightChangedListener {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
index 7019880..3687f6d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
@@ -19,6 +19,7 @@
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.content.Context;
+import android.graphics.drawable.Icon;
 import android.os.SystemClock;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.NotificationListenerService.Ranking;
@@ -26,8 +27,11 @@
 import android.service.notification.StatusBarNotification;
 import android.util.ArrayMap;
 import android.view.View;
+import android.widget.ImageView;
 import android.widget.RemoteViews;
 
+import com.android.internal.statusbar.StatusBarIcon;
+import com.android.internal.util.NotificationColorUtil;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
@@ -48,9 +52,11 @@
     public static final class Entry {
         private static final long LAUNCH_COOLDOWN = 2000;
         private static final long NOT_LAUNCHED_YET = -LAUNCH_COOLDOWN;
+        private static final int COLOR_INVALID = 1;
         public String key;
         public StatusBarNotification notification;
         public StatusBarIconView icon;
+        public StatusBarIconView expandedIcon;
         public ExpandableNotificationRow row; // the outer expanded view
         private boolean interruption;
         public boolean autoRedacted; // whether the redacted notification was generated by us
@@ -62,11 +68,12 @@
         public RemoteViews cachedHeadsUpContentView;
         public RemoteViews cachedPublicContentView;
         public CharSequence remoteInputText;
+        private int mCachedContrastColor = COLOR_INVALID;
+        private int mCachedContrastColorIsFor = COLOR_INVALID;
 
-        public Entry(StatusBarNotification n, StatusBarIconView ic) {
+        public Entry(StatusBarNotification n) {
             this.key = n.getKey();
             this.notification = n;
-            this.icon = ic;
         }
 
         public void setInterruption() {
@@ -165,6 +172,85 @@
         public boolean hasJustLaunchedFullScreenIntent() {
             return SystemClock.elapsedRealtime() < lastFullScreenIntentLaunchTime + LAUNCH_COOLDOWN;
         }
+
+        /**
+         * Create the icons for a notification
+         * @param context the context to create the icons with
+         * @param sbn the notification
+         * @throws IconException
+         */
+        public void createIcons(Context context, StatusBarNotification sbn) throws IconException {
+            Notification n = sbn.getNotification();
+            final Icon smallIcon = n.getSmallIcon();
+            if (smallIcon == null) {
+                throw new IconException("No small icon in notification from "
+                        + sbn.getPackageName());
+            }
+
+            // Construct the icon.
+            icon = new StatusBarIconView(context,
+                    sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), n);
+            icon.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
+
+            // Construct the expanded icon.
+            expandedIcon = new StatusBarIconView(context,
+                    sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), n);
+            expandedIcon.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
+            final StatusBarIcon ic = new StatusBarIcon(
+                    sbn.getUser(),
+                    sbn.getPackageName(),
+                    smallIcon,
+                    n.iconLevel,
+                    n.number,
+                    StatusBarIconView.contentDescForNotification(context, n));
+            if (!icon.set(ic) || !expandedIcon.set(ic)) {
+                icon = null;
+                expandedIcon = null;
+                throw new IconException("Couldn't create icon: " + ic);
+            }
+        }
+
+        public void setIconTag(int key, Object tag) {
+            if (icon != null) {
+                icon.setTag(key, tag);
+                expandedIcon.setTag(key, tag);
+            }
+        }
+
+        /**
+         * Update the notification icons.
+         * @param context the context to create the icons with.
+         * @param n the notification to read the icon from.
+         * @throws IconException
+         */
+        public void updateIcons(Context context, Notification n) throws IconException {
+            if (icon != null) {
+                // Update the icon
+                final StatusBarIcon ic = new StatusBarIcon(
+                        notification.getUser(),
+                        notification.getPackageName(),
+                        n.getSmallIcon(),
+                        n.iconLevel,
+                        n.number,
+                        StatusBarIconView.contentDescForNotification(context, n));
+                icon.setNotification(n);
+                expandedIcon.setNotification(n);
+                if (!icon.set(ic) || !expandedIcon.set(ic)) {
+                    throw new IconException("Couldn't update icon: " + ic);
+                }
+            }
+        }
+
+        public int getContrastedColor(Context context) {
+            int rawColor = notification.getNotification().color;
+            if (mCachedContrastColorIsFor == rawColor && mCachedContrastColor != COLOR_INVALID) {
+                return mCachedContrastColor;
+            }
+            final int contrasted = NotificationColorUtil.resolveContrastColor(context, rawColor);
+            mCachedContrastColorIsFor = rawColor;
+            mCachedContrastColor = contrasted;
+            return mCachedContrastColor;
+        }
     }
 
     private final ArrayMap<String, Entry> mEntries = new ArrayMap<>();
@@ -472,4 +558,10 @@
         public String getCurrentMediaNotificationKey();
         public NotificationGroupManager getGroupManager();
     }
+
+    public static class IconException extends Exception {
+        IconException(String error) {
+            super(error);
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java
deleted file mode 100644
index 8e8ce1a..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.TextView;
-
-import com.android.systemui.R;
-import com.android.systemui.ViewInvertHelper;
-import com.android.systemui.statusbar.phone.NotificationPanelView;
-
-/**
- * Container view for overflowing notification icons on Keyguard.
- */
-public class NotificationOverflowContainer extends ActivatableNotificationView {
-
-    private NotificationOverflowIconsView mIconsView;
-    private ViewInvertHelper mViewInvertHelper;
-    private boolean mDark;
-    private View mContent;
-
-    public NotificationOverflowContainer(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        mIconsView = (NotificationOverflowIconsView) findViewById(R.id.overflow_icons_view);
-        mIconsView.setMoreText((TextView) findViewById(R.id.more_text));
-        mIconsView.setOverflowIndicator(findViewById(R.id.more_icon_overflow));
-        mContent = findViewById(R.id.content);
-        mViewInvertHelper = new ViewInvertHelper(mContent,
-                NotificationPanelView.DOZE_ANIMATION_DURATION);
-    }
-
-    @Override
-    public void setDark(boolean dark, boolean fade, long delay) {
-        super.setDark(dark, fade, delay);
-        if (mDark == dark) return;
-        mDark = dark;
-        if (fade) {
-            mViewInvertHelper.fade(dark, delay);
-        } else {
-            mViewInvertHelper.update(dark);
-        }
-    }
-
-    @Override
-    protected View getContentView() {
-        return mContent;
-    }
-
-    public NotificationOverflowIconsView getIconsView() {
-        return mIconsView;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowIconsView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowIconsView.java
deleted file mode 100644
index 88bb714..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowIconsView.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar;
-
-import android.app.Notification;
-import android.content.Context;
-import android.graphics.PorterDuff;
-import android.util.AttributeSet;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import com.android.internal.util.NotificationColorUtil;
-import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.IconMerger;
-
-/**
- * A view to display all the overflowing icons on Keyguard.
- */
-public class NotificationOverflowIconsView extends IconMerger {
-
-    private TextView mMoreText;
-    private int mTintColor;
-    private int mIconSize;
-    private NotificationColorUtil mNotificationColorUtil;
-
-    public NotificationOverflowIconsView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        mNotificationColorUtil = NotificationColorUtil.getInstance(getContext());
-        mTintColor = getContext().getColor(R.color.keyguard_overflow_content_color);
-        mIconSize = getResources().getDimensionPixelSize(
-                com.android.internal.R.dimen.status_bar_icon_size);
-    }
-
-    public void setMoreText(TextView moreText) {
-        mMoreText = moreText;
-    }
-
-    public void addNotification(NotificationData.Entry notification) {
-        StatusBarIconView v = new StatusBarIconView(getContext(), "",
-                notification.notification.getNotification());
-        v.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
-        addView(v, mIconSize, mIconSize);
-        v.set(notification.icon.getStatusBarIcon());
-        applyColor(notification.notification.getNotification(), v);
-        updateMoreText();
-    }
-
-    private void applyColor(Notification notification, StatusBarIconView view) {
-        view.setColorFilter(mTintColor, PorterDuff.Mode.MULTIPLY);
-    }
-
-    private void updateMoreText() {
-        mMoreText.setText(
-                getResources().getString(R.string.keyguard_more_overflow_text, getChildCount()));
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
new file mode 100644
index 0000000..28b9099
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.systemui.R;
+import com.android.systemui.ViewInvertHelper;
+import com.android.systemui.statusbar.notification.NotificationUtils;
+import com.android.systemui.statusbar.phone.NotificationIconContainer;
+import com.android.systemui.statusbar.phone.NotificationPanelView;
+import com.android.systemui.statusbar.stack.AmbientState;
+import com.android.systemui.statusbar.stack.ExpandableViewState;
+import com.android.systemui.statusbar.stack.StackScrollAlgorithm;
+import com.android.systemui.statusbar.stack.StackScrollState;
+import com.android.systemui.statusbar.stack.ViewState;
+
+import java.util.ArrayList;
+import java.util.WeakHashMap;
+
+/**
+ * A notification shelf view that is placed inside the notification scroller. It manages the
+ * overflow icons that don't fit into the regular list anymore.
+ */
+public class NotificationShelf extends ActivatableNotificationView {
+
+    private ViewInvertHelper mViewInvertHelper;
+    private boolean mDark;
+    private NotificationIconContainer mNotificationIconContainer;
+    private ArrayList<StatusBarIconView> mIcons = new ArrayList<>();
+    private ShelfState mShelfState;
+    private int[] mTmp = new int[2];
+    private boolean mHideBackground;
+    private int mIconAppearTopPadding;
+
+    public NotificationShelf(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mNotificationIconContainer = (NotificationIconContainer) findViewById(R.id.content);
+        mNotificationIconContainer.setClipChildren(false);
+        mNotificationIconContainer.setClipToPadding(false);
+        setClipToActualHeight(false);
+        setClipChildren(false);
+        setClipToPadding(false);
+        mNotificationIconContainer.setShowAllIcons(false);
+        mViewInvertHelper = new ViewInvertHelper(mNotificationIconContainer,
+                NotificationPanelView.DOZE_ANIMATION_DURATION);
+        mShelfState = new ShelfState();
+        mShelfState.iconStates = mNotificationIconContainer.getIconStates();
+        initDimens();
+    }
+
+    private void initDimens() {
+        mIconAppearTopPadding = getResources().getDimensionPixelSize(
+                R.dimen.notification_icon_appear_padding);
+    }
+
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        initDimens();
+    }
+
+    @Override
+    public void setDark(boolean dark, boolean fade, long delay) {
+        super.setDark(dark, fade, delay);
+        if (mDark == dark) return;
+        mDark = dark;
+        if (fade) {
+            mViewInvertHelper.fade(dark, delay);
+        } else {
+            mViewInvertHelper.update(dark);
+        }
+    }
+
+    @Override
+    protected View getContentView() {
+        return mNotificationIconContainer;
+    }
+
+    public NotificationIconContainer getNotificationIconContainer() {
+        return mNotificationIconContainer;
+    }
+
+    @Override
+    public ExpandableViewState createNewViewState(StackScrollState stackScrollState) {
+        return mShelfState;
+    }
+
+    public void updateState(StackScrollState resultState,
+            StackScrollAlgorithm.StackScrollAlgorithmState algorithmState,
+            AmbientState ambientState) {
+        int shelfIndex = ambientState.getShelfIndex();
+        shelfIndex = shelfIndex == -1
+                ? algorithmState.visibleChildren.size() - 1
+                : shelfIndex - 1;
+        if (shelfIndex != -1) {
+            float maxShelfEnd = ambientState.getInnerHeight() + ambientState.getTopPadding()
+                    + ambientState.getStackTranslation();
+            ExpandableView lastView = algorithmState.visibleChildren.get(shelfIndex);
+            ExpandableViewState lastViewState = resultState.getViewStateForView(lastView);
+            float viewEnd = lastViewState.yTranslation + lastViewState.height;
+            mShelfState.copyFrom(lastViewState);
+            mShelfState.height = getIntrinsicHeight();
+            mShelfState.yTranslation = Math.min(viewEnd, maxShelfEnd) - mShelfState.height;
+            mShelfState.zTranslation = Math.max(mShelfState.zTranslation,
+                    ambientState.getBaseZHeight());
+            mShelfState.clipTopAmount = 0;
+            mShelfState.alpha = 1.0f;
+            mShelfState.belowShelf = false;
+            mShelfState.shadowAlpha = 1.0f;
+            mShelfState.isBottomClipped = false;
+            mShelfState.hideSensitive = false;
+
+            mShelfState.resetState();
+            float numIconsInShelf = 0.0f;
+            float viewStart;
+            float maxShelfStart = maxShelfEnd - mShelfState.height;
+            //  find the first view that doesn't overlap with the shelf
+            for (int i = shelfIndex; i >= 0; i--) {
+                lastView = algorithmState.visibleChildren.get(i);
+                lastViewState = resultState.getViewStateForView(lastView);
+                ExpandableNotificationRow row = null;
+                if (lastView instanceof ExpandableNotificationRow) {
+                    row = (ExpandableNotificationRow) lastView;
+                }
+                viewStart = lastViewState.yTranslation;
+                viewEnd = viewStart + lastView.getIntrinsicHeight();
+                if (viewEnd > maxShelfStart) {
+                    if (viewStart < maxShelfStart) {
+                        float transitionAmount = 1.0f - ((maxShelfStart - viewStart) /
+                                lastView.getIntrinsicHeight());
+                        numIconsInShelf += transitionAmount;
+                    } else {
+                        numIconsInShelf += 1.0f;
+                        lastViewState.hidden = true;
+                    }
+                }
+                if (row != null){
+                    // Not in the shelf yet, Icon needs to be placed on top of the notification icon
+                    updateIconAppearance(row.getEntry(), lastViewState, mShelfState);
+                }
+            }
+            mShelfState.iconStates = mNotificationIconContainer.calculateIconStates(
+                    numIconsInShelf);
+            mShelfState.hidden = numIconsInShelf == 0.0f;
+            mShelfState.hideBackground = numIconsInShelf < 1.0f;
+        } else {
+            mShelfState.hideBackground = true;
+            mShelfState.hidden = true;
+            mShelfState.location = ExpandableViewState.LOCATION_GONE;
+        }
+    }
+
+    private void updateIconAppearance(NotificationData.Entry entry, ExpandableViewState rowState,
+            ShelfState shelfState) {
+        StatusBarIconView icon = entry.expandedIcon;
+        ViewState iconState = shelfState.iconStates.get(icon);
+        View rowIcon = entry.row.getNotificationIcon();
+        float notificationIconPosition = rowState.yTranslation;
+        float notificationIconSize = 0.0f;
+        int iconTopPadding;
+        if (rowIcon != null) {
+            iconTopPadding = getIconTopPadding(rowIcon);
+            notificationIconSize = rowIcon.getHeight();
+        } else {
+            iconTopPadding = mIconAppearTopPadding;
+        }
+        notificationIconPosition += iconTopPadding;
+        float shelfIconPosition = mShelfState.yTranslation + icon.getTop();
+        shelfIconPosition += ((1.0f - icon.getIconScale()) * icon.getHeight()) / 2.0f;
+        float transitionDistance = getIntrinsicHeight() * 1.5f;
+        float transformationStartPosition = mShelfState.yTranslation - transitionDistance;
+        float transitionAmount = 0.0f;
+        if (rowState.yTranslation < transformationStartPosition) {
+            // We simply place it on the icon of the notification
+            iconState.yTranslation = notificationIconPosition - shelfIconPosition;
+        } else {
+            transitionAmount = (rowState.yTranslation - transformationStartPosition)
+                    / transitionDistance;
+            float startPosition = transformationStartPosition + iconTopPadding;
+            iconState.yTranslation = NotificationUtils.interpolate(
+                    startPosition - shelfIconPosition, 0, transitionAmount);
+        }
+        float shelfIconSize = icon.getHeight() * icon.getIconScale();
+        Float newSize = NotificationUtils.interpolate(notificationIconSize, shelfIconSize,
+                transitionAmount);
+        iconState.scaleX = newSize / icon.getHeight();
+        iconState.scaleY = iconState.scaleX;
+        iconState.hidden = transitionAmount == 0.0f;
+        if (!entry.row.isShowingIcon()) {
+            iconState.alpha = transitionAmount;
+        }
+    }
+
+    private int getIconTopPadding(View icon) {
+        View view = icon;
+        int topPadding = 0;
+        while (view.getParent() instanceof ViewGroup) {
+            topPadding += view.getTop();
+            view = (View) view.getParent();
+            if (view instanceof ExpandableNotificationRow) {
+                return topPadding;
+            }
+        }
+        return topPadding;
+    }
+
+    public int getNotificationMergeSize() {
+        return getIntrinsicHeight();
+    }
+
+    @Override
+    public boolean hasNoContentHeight() {
+        return true;
+    }
+    
+    private void setHideBackground(boolean hideBackground) {
+        mHideBackground = hideBackground;
+        updateBackground();
+    }
+
+    @Override
+    protected boolean shouldHideBackground() {
+        return super.shouldHideBackground() || mHideBackground;
+    }
+
+    private class ShelfState extends ExpandableViewState {
+        private WeakHashMap<View, ViewState> iconStates = new WeakHashMap<>();
+        private boolean hideBackground;
+
+        @Override
+        public void applyToView(View view) {
+            super.applyToView(view);
+            mNotificationIconContainer.applyIconStates(iconStates);
+            setHideBackground(hideBackground);
+        }
+
+        public void resetState() {
+            mNotificationIconContainer.resetViewStates(iconStates);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index cdfdad4..03e3662 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -54,6 +54,7 @@
     private Notification mNotification;
     private final boolean mBlocked;
     private int mDensity;
+    private float mIconScale = 1.0f;
 
     public StatusBarIconView(Context context, String slot, Notification notification) {
         this(context, slot, notification, false);
@@ -86,9 +87,13 @@
         Resources res = mContext.getResources();
         final int outerBounds = res.getDimensionPixelSize(R.dimen.status_bar_icon_size);
         final int imageBounds = res.getDimensionPixelSize(R.dimen.status_bar_icon_drawing_size);
-        final float scale = (float)imageBounds / (float)outerBounds;
-        setScaleX(scale);
-        setScaleY(scale);
+        mIconScale = (float)imageBounds / (float)outerBounds;
+        setScaleX(mIconScale);
+        setScaleY(mIconScale);
+    }
+
+    public float getIconScale() {
+        return mIconScale;
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/IconMerger.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/IconMerger.java
deleted file mode 100644
index f86badb..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/IconMerger.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone;
-
-import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.LinearLayout;
-
-import com.android.systemui.R;
-
-public class IconMerger extends LinearLayout {
-    private static final String TAG = "IconMerger";
-    private static final boolean DEBUG = false;
-
-    private int mIconSize;
-    private int mIconHPadding;
-
-    private View mMoreView;
-
-    public IconMerger(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        reloadDimens();
-        if (DEBUG) {
-            setBackgroundColor(0x800099FF);
-        }
-    }
-
-    private void reloadDimens() {
-        Resources res = mContext.getResources();
-        mIconSize = res.getDimensionPixelSize(R.dimen.status_bar_icon_size);
-        mIconHPadding = res.getDimensionPixelSize(R.dimen.status_bar_icon_padding);
-    }
-
-    @Override
-    protected void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
-        reloadDimens();
-    }
-
-    public void setOverflowIndicator(View v) {
-        mMoreView = v;
-    }
-
-    private int getFullIconWidth() {
-        return mIconSize + 2 * mIconHPadding;
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-        // we need to constrain this to an integral multiple of our children
-        int width = getMeasuredWidth();
-        setMeasuredDimension(width - (width % getFullIconWidth()), getMeasuredHeight());
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int l, int t, int r, int b) {
-        super.onLayout(changed, l, t, r, b);
-        checkOverflow(r - l);
-    }
-
-    private void checkOverflow(int width) {
-        if (mMoreView == null) return;
-
-        final int N = getChildCount();
-        int visibleChildren = 0;
-        for (int i=0; i<N; i++) {
-            if (getChildAt(i).getVisibility() != GONE) visibleChildren++;
-        }
-        final boolean overflowShown = (mMoreView.getVisibility() == View.VISIBLE);
-        // let's assume we have one more slot if the more icon is already showing
-        if (overflowShown) visibleChildren --;
-        final boolean moreRequired = visibleChildren * getFullIconWidth() > width;
-        if (moreRequired != overflowShown) {
-            post(new Runnable() {
-                @Override
-                public void run() {
-                    mMoreView.setVisibility(moreRequired ? View.VISIBLE : View.GONE);
-                }
-            });
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index 784cb48..70beac8ea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -80,7 +80,7 @@
         mClockYFractionMin = res.getFraction(R.fraction.keyguard_clock_y_fraction_min, 1, 1);
         mClockYFractionMax = res.getFraction(R.fraction.keyguard_clock_y_fraction_max, 1, 1);
         mMoreCardNotificationAmount =
-                (float) res.getDimensionPixelSize(R.dimen.notification_summary_height) /
+                (float) res.getDimensionPixelSize(R.dimen.notification_shelf_height) /
                         res.getDimensionPixelSize(R.dimen.notification_min_height);
         mDensity = res.getDisplayMetrics().density;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index cbaab14..72e84a4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -8,16 +8,20 @@
 import android.support.annotation.NonNull;
 import android.view.LayoutInflater;
 import android.view.View;
-import android.widget.ImageView;
+import android.view.ViewGroup;
 import android.widget.LinearLayout;
 
 import com.android.internal.util.NotificationColorUtil;
 import com.android.systemui.R;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
 import com.android.systemui.statusbar.NotificationData;
+import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.StatusBarIconView;
 import com.android.systemui.statusbar.notification.NotificationUtils;
+import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
 
 import java.util.ArrayList;
+import java.util.function.Function;
 
 /**
  * A controller for the space in the status bar to the left of the system icons. This area is
@@ -32,13 +36,17 @@
 
     private PhoneStatusBar mPhoneStatusBar;
     protected View mNotificationIconArea;
-    private IconMerger mNotificationIcons;
-    private ImageView mMoreIcon;
+    private NotificationShelf mNotificationIconAreaScroller;
+    private NotificationIconContainer mNotificationIcons;
+    private NotificationIconContainer mNotificationIconsScroller;
     private final Rect mTintArea = new Rect();
+    private NotificationStackScrollLayout mNotificationScrollLayout;
+    private Context mContext;
 
     public NotificationIconAreaController(Context context, PhoneStatusBar phoneStatusBar) {
         mPhoneStatusBar = phoneStatusBar;
         mNotificationColorUtil = NotificationColorUtil.getInstance(context);
+        mContext = context;
 
         initializeNotificationAreaViews(context);
     }
@@ -55,15 +63,13 @@
 
         LayoutInflater layoutInflater = LayoutInflater.from(context);
         mNotificationIconArea = inflateIconArea(layoutInflater);
+        mNotificationIcons = (NotificationIconContainer) mNotificationIconArea.findViewById(
+                R.id.notificationIcons);
 
-        mNotificationIcons =
-                (IconMerger) mNotificationIconArea.findViewById(R.id.notificationIcons);
+        mNotificationIconAreaScroller = mPhoneStatusBar.getNotificationShelf();
+        mNotificationIconsScroller = mNotificationIconAreaScroller.getNotificationIconContainer();
 
-        mMoreIcon = (ImageView) mNotificationIconArea.findViewById(R.id.moreIcon);
-        if (mMoreIcon != null) {
-            mMoreIcon.setImageTintList(ColorStateList.valueOf(mIconTint));
-            mNotificationIcons.setOverflowIndicator(mMoreIcon);
-        }
+        mNotificationScrollLayout = mPhoneStatusBar.getNotificationScrollLayout();
     }
 
     public void onDensityOrFontScaleChanged(Context context) {
@@ -114,9 +120,7 @@
      */
     public void setIconTint(int iconTint) {
         mIconTint = iconTint;
-        if (mMoreIcon != null) {
-            mMoreIcon.setImageTintList(ColorStateList.valueOf(mIconTint));
-        }
+        mNotificationIcons.setIconTint(mIconTint);
         applyNotificationIconsTint();
     }
 
@@ -144,24 +148,54 @@
      * Updates the notifications with the given list of notifications to display.
      */
     public void updateNotificationIcons(NotificationData notificationData) {
-        final LinearLayout.LayoutParams params = generateIconLayoutParams();
 
-        ArrayList<NotificationData.Entry> activeNotifications =
-                notificationData.getActiveNotifications();
-        final int size = activeNotifications.size();
-        ArrayList<StatusBarIconView> toShow = new ArrayList<>(size);
+        updateIconsForLayout(notificationData, entry -> entry.icon, mNotificationIcons);
+        updateIconsForLayout(notificationData, entry -> entry.expandedIcon,
+                mNotificationIconsScroller);
+
+        applyNotificationIconsTint();
+        ArrayList<NotificationData.Entry> activeNotifications
+                = notificationData.getActiveNotifications();
+        for (int i = 0; i < activeNotifications.size(); i++) {
+            NotificationData.Entry entry = activeNotifications.get(i);
+            boolean isPreL = Boolean.TRUE.equals(entry.expandedIcon.getTag(R.id.icon_is_pre_L));
+            boolean colorize = !isPreL
+                    || NotificationUtils.isGrayscale(entry.expandedIcon, mNotificationColorUtil);
+            if (colorize) {
+                int color = entry.getContrastedColor(mContext);
+                entry.expandedIcon.setImageTintList(ColorStateList.valueOf(color));
+            }
+        }
+    }
+
+    /**
+     * Updates the notification icons for a host layout. This will ensure that the notification
+     * host layout will have the same icons like the ones in here.
+     *
+     * @param notificationData the notification data to look up which notifications are relevant
+     * @param function A function to look up an icon view based on an entry
+     * @param hostLayout which layout should be updated
+     */
+    private void updateIconsForLayout(NotificationData notificationData,
+            Function<NotificationData.Entry, StatusBarIconView> function,
+            ViewGroup hostLayout) {
+        ArrayList<StatusBarIconView> toShow = new ArrayList<>(
+                mNotificationScrollLayout.getChildCount());
 
         // Filter out ambient notifications and notification children.
-        for (int i = 0; i < size; i++) {
-            NotificationData.Entry ent = activeNotifications.get(i);
-            if (shouldShowNotification(ent, notificationData)) {
-                toShow.add(ent.icon);
+        for (int i = 0; i < mNotificationScrollLayout.getChildCount(); i++) {
+            View view = mNotificationScrollLayout.getChildAt(i);
+            if (view instanceof ExpandableNotificationRow) {
+                NotificationData.Entry ent = ((ExpandableNotificationRow) view).getEntry();
+                if (shouldShowNotification(ent, notificationData)) {
+                    toShow.add(function.apply(ent));
+                }
             }
         }
 
         ArrayList<View> toRemove = new ArrayList<>();
-        for (int i = 0; i < mNotificationIcons.getChildCount(); i++) {
-            View child = mNotificationIcons.getChildAt(i);
+        for (int i = 0; i < hostLayout.getChildCount(); i++) {
+            View child = hostLayout.getChildAt(i);
             if (!toShow.contains(child)) {
                 toRemove.add(child);
             }
@@ -169,29 +203,28 @@
 
         final int toRemoveCount = toRemove.size();
         for (int i = 0; i < toRemoveCount; i++) {
-            mNotificationIcons.removeView(toRemove.get(i));
+            hostLayout.removeView(toRemove.get(i));
         }
 
+        final LinearLayout.LayoutParams params = generateIconLayoutParams();
         for (int i = 0; i < toShow.size(); i++) {
             View v = toShow.get(i);
             if (v.getParent() == null) {
-                mNotificationIcons.addView(v, i, params);
+                hostLayout.addView(v, i, params);
             }
         }
 
         // Re-sort notification icons
-        final int childCount = mNotificationIcons.getChildCount();
+        final int childCount = hostLayout.getChildCount();
         for (int i = 0; i < childCount; i++) {
-            View actual = mNotificationIcons.getChildAt(i);
+            View actual = hostLayout.getChildAt(i);
             StatusBarIconView expected = toShow.get(i);
             if (actual == expected) {
                 continue;
             }
-            mNotificationIcons.removeView(expected);
-            mNotificationIcons.addView(expected, i);
+            hostLayout.removeView(expected);
+            hostLayout.addView(expected, i);
         }
-
-        applyNotificationIconsTint();
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
new file mode 100644
index 0000000..bf7886d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+
+import com.android.systemui.statusbar.AlphaOptimizedFrameLayout;
+import com.android.systemui.statusbar.stack.ViewState;
+
+import java.util.WeakHashMap;
+
+/**
+ * A container for notification icons. It handles overflowing icons properly and positions them
+ * correctly on the screen.
+ */
+public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
+    private static final String TAG = "NotificationIconContainer";
+
+    private boolean mShowAllIcons = true;
+    private int mIconTint;
+    private WeakHashMap<View, ViewState> mIconStates = new WeakHashMap<>();
+
+    public NotificationIconContainer(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        float centerY = getHeight() / 2.0f;
+        // we layout all our children on the left at the top
+        for (int i = 0; i < getChildCount(); i++) {
+            View child = getChildAt(i);
+            // We need to layout all children even the GONE ones, such that the heights are
+            // calculated correctly as they are used to calculate how many we can fit on the screen
+            int width = child.getMeasuredWidth();
+            int height = child.getMeasuredHeight();
+            int top = (int) (centerY - height / 2.0f);
+            child.layout(0, top, width, top + height);
+        }
+        if (mShowAllIcons) {
+            resetViewStates(mIconStates);
+            calculateIconStates(getChildCount());
+            applyIconStates(mIconStates);
+        }
+    }
+
+    public void applyIconStates(WeakHashMap<View, ViewState> iconStates) {
+        for (int i = 0; i < getChildCount(); i++) {
+            View child = getChildAt(i);
+            ViewState childState = iconStates.get(child);
+            if (childState != null) {
+                childState.applyToView(child);
+            }
+        }
+    }
+
+    @Override
+    public void onViewAdded(View child) {
+        super.onViewAdded(child);
+        mIconStates.put(child, new ViewState());
+    }
+
+    @Override
+    public void onViewRemoved(View child) {
+        super.onViewRemoved(child);
+        mIconStates.remove(child);
+    }
+
+    public void setIconTint(int iconTint) {
+        mIconTint = iconTint;
+    }
+
+    public void resetViewStates(WeakHashMap<View, ViewState> viewStates) {
+        for (int i = 0; i < getChildCount(); i++) {
+            View view = getChildAt(i);
+            ViewState iconState = mIconStates.get(view);
+            iconState.initFrom(view);
+        }
+    }
+
+    /**
+     * Gets a new state based on the number of visible icons starting from the right.
+     * If this is not a whole number, the fraction means by how much the icon is appearing.
+     */
+    public WeakHashMap<View, ViewState> calculateIconStates(float numberOfVisibleIcons) {
+        int childCount = getChildCount();
+        float visibleIconStart = childCount - numberOfVisibleIcons;
+        int firstIconIndex = (int) visibleIconStart;
+        float translationX = 0.0f;
+        for (int i = 0; i < childCount; i++) {
+            View view = getChildAt(i);
+            ViewState iconState = mIconStates.get(view);
+            if (i >= firstIconIndex) {
+                iconState.xTranslation = translationX;
+                float appearAmount = 1.0f;
+                if (i == firstIconIndex) {
+                    appearAmount = 1.0f - (visibleIconStart - firstIconIndex);
+                }
+                translationX += appearAmount * view.getWidth();
+            }
+        }
+        return mIconStates;
+    }
+
+    public WeakHashMap<View, ViewState> getIconStates() {
+        return mIconStates;
+    }
+
+    /**
+     * Sets whether the layout should always show all icons.
+     * If this is true, the icon positions will be updated on layout.
+     * If this if false, the layout is managed from the outside and layouting won't trigger a
+     * repositioning of the icons.
+     */
+    public void setShowAllIcons(boolean showAllIcons) {
+        mShowAllIcons = showAllIcons;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 068631d..dc30999 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -404,10 +404,11 @@
         int notificationPadding = Math.max(1, getResources().getDimensionPixelSize(
                 R.dimen.notification_divider_height));
         final int overflowheight = getResources().getDimensionPixelSize(
-                R.dimen.notification_summary_height);
-        float bottomStackSize = mNotificationStackScroller.getKeyguardBottomStackSize();
+                R.dimen.notification_shelf_height);
+        float shelfSize = mNotificationStackScroller.getNotificationShelf().getIntrinsicHeight()
+                + notificationPadding;
         float availableSpace = mNotificationStackScroller.getHeight() - minPadding - overflowheight
-                - bottomStackSize;
+                - shelfSize;
         int count = 0;
         for (int i = 0; i < mNotificationStackScroller.getChildCount(); i++) {
             ExpandableView child = (ExpandableView) mNotificationStackScroller.getChildAt(i);
@@ -429,6 +430,16 @@
             availableSpace -= child.getMinHeight() + notificationPadding;
             if (availableSpace >= 0 && count < maximum) {
                 count++;
+            } else if (availableSpace > -shelfSize) {
+                // if we are exactly the last view, then we can show us still!
+                for (int j = i + 1; j < mNotificationStackScroller.getChildCount(); j++) {
+                    if (mNotificationStackScroller.getChildAt(j)
+                            instanceof ExpandableNotificationRow) {
+                        return count;
+                    }
+                }
+                count++;
+                return count;
             } else {
                 return count;
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 9b93527..b847cdc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -164,7 +164,7 @@
 import com.android.systemui.statusbar.KeyguardIndicationController;
 import com.android.systemui.statusbar.NotificationData;
 import com.android.systemui.statusbar.NotificationData.Entry;
-import com.android.systemui.statusbar.NotificationOverflowContainer;
+import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.ScrimView;
 import com.android.systemui.statusbar.SignalClusterView;
@@ -659,10 +659,12 @@
         array.clear();
     }
 
-    private final View.OnClickListener mOverflowClickListener = new View.OnClickListener() {
+    private final View.OnClickListener mShelfClickListener = new View.OnClickListener() {
         @Override
         public void onClick(View v) {
-            goToLockedShade(null);
+            if (mState == StatusBarState.KEYGUARD) {
+                goToLockedShade(null);
+            }
         }
     };
     private HashMap<ExpandableNotificationRow, List<ExpandableNotificationRow>> mTmpChildOrderMap
@@ -812,7 +814,7 @@
         mStackScroller.setHeadsUpManager(mHeadsUpManager);
         mGroupManager.setOnGroupChangeListener(mStackScroller);
 
-        inflateOverflowContainer();
+        inflateShelf();
         inflateEmptyShadeView();
         inflateDismissView();
         mExpandedContents = mStackScroller;
@@ -1049,13 +1051,13 @@
         return new BatteryControllerImpl(mContext);
     }
 
-    private void inflateOverflowContainer() {
-        mKeyguardIconOverflowContainer =
-                (NotificationOverflowContainer) LayoutInflater.from(mContext).inflate(
-                        R.layout.status_bar_notification_keyguard_overflow, mStackScroller, false);
-        mKeyguardIconOverflowContainer.setOnActivatedListener(this);
-        mKeyguardIconOverflowContainer.setOnClickListener(mOverflowClickListener);
-        mStackScroller.setOverflowContainer(mKeyguardIconOverflowContainer);
+    private void inflateShelf() {
+        mNotificationShelf =
+                (NotificationShelf) LayoutInflater.from(mContext).inflate(
+                        R.layout.status_bar_notification_icon_container, mStackScroller, false);
+        mNotificationShelf.setOnActivatedListener(this);
+        mNotificationShelf.setOnClickListener(mShelfClickListener);
+        mStackScroller.setShelf(mNotificationShelf);
     }
 
     @Override
@@ -1072,7 +1074,7 @@
         updateClearAll();
         inflateEmptyShadeView();
         updateEmptyShadeView();
-        inflateOverflowContainer();
+        inflateShelf();
         mStatusBarKeyguardViewManager.onDensityOrFontScaleChanged();
         mUserInfoController.onDensityOrFontScaleChanged();
         if (mUserSwitcherController != null) {
@@ -1866,7 +1868,7 @@
         mTmpChildOrderMap.clear();
 
         updateRowStates();
-        updateSpeedbump();
+        updateShelfIndex();
         updateClearAll();
         updateEmptyShadeView();
 
@@ -1987,8 +1989,8 @@
         mNotificationPanel.setShadeEmpty(showEmptyShade);
     }
 
-    private void updateSpeedbump() {
-        int speedbumpIndex = -1;
+    private void updateShelfIndex() {
+        int shelfIndex = -1;
         int currentIndex = 0;
         final int N = mStackScroller.getChildCount();
         for (int i = 0; i < N; i++) {
@@ -1998,12 +2000,12 @@
             }
             ExpandableNotificationRow row = (ExpandableNotificationRow) view;
             if (mNotificationData.isAmbient(row.getStatusBarNotification().getKey())) {
-                speedbumpIndex = currentIndex;
+                shelfIndex = currentIndex;
                 break;
             }
             currentIndex++;
         }
-        mStackScroller.updateSpeedBumpIndex(speedbumpIndex);
+        mStackScroller.updateShelfIndex(shelfIndex);
     }
 
     public static boolean isTopLevelChild(Entry entry) {
@@ -2694,6 +2696,14 @@
         mFalsingManager.onScreenOff();
     }
 
+    public NotificationShelf getNotificationShelf() {
+        return mNotificationShelf;
+    }
+
+    public NotificationStackScrollLayout getNotificationScrollLayout() {
+        return mStackScroller;
+    }
+
     public boolean isPulsing() {
         return mDozeScrimController.isPulsing();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index dbe7f96..a948a08 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -44,6 +44,7 @@
 import com.android.systemui.R;
 import com.android.systemui.SystemUIFactory;
 import com.android.systemui.statusbar.NotificationData;
+import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.SignalClusterView;
 import com.android.systemui.statusbar.StatusBarIconView;
 import com.android.systemui.tuner.TunerService;
@@ -74,6 +75,7 @@
 
     private NotificationIconAreaController mNotificationIconAreaController;
     private View mNotificationIconAreaInner;
+    private NotificationShelf mNotificationShelf;
 
     private BatteryMeterView mBatteryMeterView;
     private BatteryMeterView mBatteryMeterViewKeyguard;
@@ -123,6 +125,7 @@
         mStatusIcons = (LinearLayout) statusBar.findViewById(R.id.statusIcons);
         mSignalCluster = (SignalClusterView) statusBar.findViewById(R.id.signal_cluster);
 
+        mNotificationShelf = phoneStatusBar.getNotificationShelf();
         mNotificationIconAreaController = SystemUIFactory.getInstance()
                 .createNotificationIconAreaController(context, phoneStatusBar);
         mNotificationIconAreaInner =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
index 81da672..9a3dbc8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
@@ -16,9 +16,12 @@
 
 package com.android.systemui.statusbar.stack;
 
+import android.content.Context;
 import android.view.View;
 
+import com.android.systemui.R;
 import com.android.systemui.statusbar.ActivatableNotificationView;
+import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
 import java.util.ArrayList;
@@ -33,7 +36,7 @@
     private ActivatableNotificationView mActivatedChild;
     private float mOverScrollTopAmount;
     private float mOverScrollBottomAmount;
-    private int mSpeedBumpIndex = -1;
+    private int mShelfIndex = -1;
     private boolean mDark;
     private boolean mHideSensitive;
     private HeadsUpManager mHeadsUpManager;
@@ -44,6 +47,37 @@
     private float mMaxHeadsUpTranslation;
     private boolean mDismissAllInProgress;
     private int mLayoutMinHeight;
+    private NotificationShelf mShelf;
+    private int mZDistanceBetweenElements;
+    private int mBaseZHeight;
+
+    public AmbientState(Context context) {
+        reload(context);
+    }
+
+    /**
+     * Reload the dimens e.g. if the density changed.
+     */
+    public void reload(Context context) {
+        mZDistanceBetweenElements = Math.max(1, context.getResources()
+                .getDimensionPixelSize(R.dimen.z_distance_between_notifications));
+        mBaseZHeight = (StackScrollAlgorithm.MAX_ITEMS_IN_BOTTOM_STACK + 1)
+                * mZDistanceBetweenElements;
+    }
+
+    /**
+     * @return the basic Z height on which notifications remain.
+     */
+    public int getBaseZHeight() {
+        return mBaseZHeight;
+    }
+
+    /**
+     * @return the distance in Z between two overlaying notifications.
+     */
+    public int getZDistanceBetweenElements() {
+        return mZDistanceBetweenElements;
+    }
 
     public int getScrollY() {
         return mScrollY;
@@ -118,12 +152,12 @@
         return top ? mOverScrollTopAmount : mOverScrollBottomAmount;
     }
 
-    public int getSpeedBumpIndex() {
-        return mSpeedBumpIndex;
+    public int getShelfIndex() {
+        return mShelfIndex;
     }
 
-    public void setSpeedBumpIndex(int speedBumpIndex) {
-        mSpeedBumpIndex = speedBumpIndex;
+    public void setShelfIndex(int shelfIndex) {
+        mShelfIndex = shelfIndex;
     }
 
     public void setHeadsUpManager(HeadsUpManager headsUpManager) {
@@ -181,4 +215,12 @@
     public void setLayoutMinHeight(int layoutMinHeight) {
         mLayoutMinHeight = layoutMinHeight;
     }
+
+    public void setShelf(NotificationShelf shelf) {
+        mShelf = shelf;
+    }
+
+    public NotificationShelf getShelf() {
+        return mShelf;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ExpandableViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ExpandableViewState.java
index 02b7ce3..c78def4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ExpandableViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ExpandableViewState.java
@@ -59,15 +59,15 @@
     public static final int LOCATION_BOTTOM_STACK_HIDDEN = 0x10;
 
     /**
-     * The view isn't layouted at all.
-     * */
+     * The view isn't laid out at all.
+     */
     public static final int LOCATION_GONE = 0x40;
 
     public int height;
     public boolean dimmed;
     public boolean dark;
     public boolean hideSensitive;
-    public boolean belowSpeedBump;
+    public boolean belowShelf;
     public float shadowAlpha;
 
     /**
@@ -103,7 +103,7 @@
             shadowAlpha = svs.shadowAlpha;
             dark = svs.dark;
             hideSensitive = svs.hideSensitive;
-            belowSpeedBump = svs.belowSpeedBump;
+            belowShelf = svs.belowShelf;
             clipTopAmount = svs.clipTopAmount;
             notGoneIndex = svs.notGoneIndex;
             location = svs.location;
@@ -144,7 +144,7 @@
                     this.hideSensitive, false /* animated */, 0 /* delay */, 0 /* duration */);
 
             // apply below shelf speedBump
-            expandableView.setBelowSpeedBump(this.belowSpeedBump);
+            expandableView.setBelowShelf(this.belowShelf);
 
             // apply dark
             expandableView.setDark(this.dark, false /* animate */, 0 /* delay */);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
index 4b944d1..017c950 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
@@ -473,7 +473,7 @@
             childState.dimmed = parentState.dimmed;
             childState.dark = parentState.dark;
             childState.hideSensitive = parentState.hideSensitive;
-            childState.belowSpeedBump = parentState.belowSpeedBump;
+            childState.belowShelf = parentState.belowShelf;
             childState.clipTopAmount = 0;
             childState.alpha = 0;
             if (i < firstOverflowIndex) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index a8c082e..74ca2032 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -69,7 +69,7 @@
 import com.android.systemui.statusbar.ExpandableNotificationRow;
 import com.android.systemui.statusbar.ExpandableView;
 import com.android.systemui.statusbar.NotificationGuts;
-import com.android.systemui.statusbar.NotificationOverflowContainer;
+import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.NotificationSettingsIconRow;
 import com.android.systemui.statusbar.NotificationSettingsIconRow.SettingsIconRowListener;
 import com.android.systemui.statusbar.StackScrollerDecorView;
@@ -151,7 +151,7 @@
      * The current State this Layout is in
      */
     private StackScrollState mCurrentStackScrollState = new StackScrollState(this);
-    private AmbientState mAmbientState = new AmbientState();
+    private final AmbientState mAmbientState;
     private NotificationGroupManager mGroupManager;
     private HashSet<View> mChildrenToAddAnimated = new HashSet<>();
     private ArrayList<View> mAddedHeadsUpChildren = new ArrayList<>();
@@ -261,7 +261,6 @@
     private boolean mTrackingHeadsUp;
     private ScrimController mScrimController;
     private boolean mForceNoOverlappingRendering;
-    private NotificationOverflowContainer mOverflowContainer;
     private final ArrayList<Pair<ExpandableNotificationRow, Boolean>> mTmpList = new ArrayList<>();
     private FalsingManager mFalsingManager;
     private boolean mAnimationRunning;
@@ -354,6 +353,7 @@
     private boolean mQsExpanded;
     private boolean mForwardScrollable;
     private boolean mBackwardScrollable;
+    private NotificationShelf mShelf;
 
     public NotificationStackScrollLayout(Context context) {
         this(context, null);
@@ -370,6 +370,7 @@
     public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr,
             int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
+        mAmbientState = new AmbientState(context);
         mBgColor = context.getColor(R.color.notification_shade_background_color);
         int minHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height);
         int maxHeight = getResources().getDimensionPixelSize(R.dimen.notification_max_height);
@@ -462,6 +463,7 @@
         mBottomStackPeekSize = context.getResources()
                 .getDimensionPixelSize(R.dimen.bottom_stack_peek_amount);
         mStackScrollAlgorithm.initView(context);
+        mAmbientState.reload(context);
         mPaddingBetweenElements = Math.max(1, context.getResources()
                 .getDimensionPixelSize(R.dimen.notification_divider_height));
         mIncreasedPaddingBetweenElements = context.getResources()
@@ -529,8 +531,10 @@
         }
     }
 
-    public void updateSpeedBumpIndex(int newIndex) {
-        mAmbientState.setSpeedBumpIndex(newIndex);
+    public void updateShelfIndex(int newIndex) {
+        mAmbientState.setShelfIndex(newIndex);
+        int iconIndex = newIndex == -1 ? getChildCount() - 3 : newIndex;
+        changeViewPosition(mShelf, iconIndex);
     }
 
     public void setChildLocationsChangedListener(OnChildLocationsChangedListener listener) {
@@ -1845,7 +1849,8 @@
         float previousIncreasedAmount = 0.0f;
         for (int i = 0; i < getChildCount(); i++) {
             ExpandableView expandableView = (ExpandableView) getChildAt(i);
-            if (expandableView.getVisibility() != View.GONE) {
+            if (expandableView.getVisibility() != View.GONE
+                    && !expandableView.hasNoContentHeight()) {
                 float increasedPaddingAmount = expandableView.getIncreasedPaddingAmount();
                 if (height != 0) {
                     height += (int) NotificationUtils.interpolate(
@@ -2536,7 +2541,7 @@
         for (int i = 0; i < getChildCount(); i++) {
             ExpandableView child = (ExpandableView) getChildAt(i);
             boolean notGone = child.getVisibility() != View.GONE;
-            if (notGone) {
+            if (notGone && !child.hasNoContentHeight()) {
                 float increasedPaddingAmount = child.getIncreasedPaddingAmount();
                 if (position != 0) {
                     position += (int) NotificationUtils.interpolate(
@@ -3538,50 +3543,6 @@
         }
     }
 
-    public void setOverflowContainer(NotificationOverflowContainer overFlowContainer) {
-        int index = -1;
-        if (mOverflowContainer != null) {
-            index = indexOfChild(mOverflowContainer);
-            removeView(mOverflowContainer);
-        }
-        mOverflowContainer = overFlowContainer;
-        addView(mOverflowContainer, index);
-    }
-
-    public void updateOverflowContainerVisibility(boolean visible) {
-        int oldVisibility = mOverflowContainer.willBeGone() ? GONE
-                : mOverflowContainer.getVisibility();
-        final int newVisibility = visible ? VISIBLE : GONE;
-        if (oldVisibility != newVisibility) {
-            Runnable onFinishedRunnable = new Runnable() {
-                @Override
-                public void run() {
-                    mOverflowContainer.setVisibility(newVisibility);
-                    mOverflowContainer.setWillBeGone(false);
-                    updateContentHeight();
-                    notifyHeightChangeListener(mOverflowContainer);
-                }
-            };
-            if (!mAnimationsEnabled || !mIsExpanded) {
-                mOverflowContainer.cancelAppearDrawing();
-                onFinishedRunnable.run();
-            } else if (newVisibility != GONE) {
-                mOverflowContainer.performAddAnimation(0,
-                        StackStateAnimator.ANIMATION_DURATION_STANDARD);
-                mOverflowContainer.setVisibility(newVisibility);
-                mOverflowContainer.setWillBeGone(false);
-                updateContentHeight();
-                notifyHeightChangeListener(mOverflowContainer);
-            } else {
-                mOverflowContainer.performRemoveAnimation(
-                        StackStateAnimator.ANIMATION_DURATION_STANDARD,
-                        0.0f,
-                        onFinishedRunnable);
-                mOverflowContainer.setWillBeGone(true);
-            }
-        }
-    }
-
     public void updateDismissView(boolean visible) {
         int oldVisibility = mDismissView.willBeGone() ? GONE : mDismissView.getVisibility();
         int newVisibility = visible ? VISIBLE : GONE;
@@ -3949,6 +3910,21 @@
         }
     }
 
+    public void setShelf(NotificationShelf shelf) {
+        mShelf = shelf;
+        int index = -1;
+        if (mShelf != null) {
+            index = indexOfChild(mShelf);
+            removeView(mShelf);
+        }
+        addView(mShelf, index);
+        mAmbientState.setShelf(shelf);
+    }
+
+    public NotificationShelf getNotificationShelf() {
+        return mShelf;
+    }
+
     /**
      * A listener that is notified when some child locations might have changed.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
index 3a6d757..4d2ea50 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
@@ -24,6 +24,7 @@
 import com.android.systemui.R;
 import com.android.systemui.statusbar.ExpandableNotificationRow;
 import com.android.systemui.statusbar.ExpandableView;
+import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.notification.FakeShadowView;
 import com.android.systemui.statusbar.notification.NotificationUtils;
 
@@ -40,14 +41,12 @@
 
     private static final String LOG_TAG = "StackScrollAlgorithm";
 
-    private static final int MAX_ITEMS_IN_BOTTOM_STACK = 3;
+    public static final int MAX_ITEMS_IN_BOTTOM_STACK = 3;
 
     private int mPaddingBetweenElements;
     private int mIncreasedPaddingBetweenElements;
     private int mCollapsedSize;
     private int mBottomStackPeekSize;
-    private int mZDistanceBetweenElements;
-    private int mZBasicHeight;
 
     private StackIndentationFunctor mBottomStackIndentationFunctor;
 
@@ -76,9 +75,6 @@
                 .getDimensionPixelSize(R.dimen.notification_min_height);
         mBottomStackPeekSize = context.getResources()
                 .getDimensionPixelSize(R.dimen.bottom_stack_peek_amount);
-        mZDistanceBetweenElements = Math.max(1, context.getResources()
-                .getDimensionPixelSize(R.dimen.z_distance_between_notifications));
-        mZBasicHeight = (MAX_ITEMS_IN_BOTTOM_STACK + 1) * mZDistanceBetweenElements;
         mBottomStackSlowDownLength = context.getResources()
                 .getDimensionPixelSize(R.dimen.bottom_stack_slow_down_length);
         mBottomStackIndentationFunctor = new PiecewiseLinearIndentationFunctor(
@@ -107,7 +103,7 @@
         handleDraggedViews(ambientState, resultState, algorithmState);
         updateDimmedActivatedHideSensitive(ambientState, resultState, algorithmState);
         updateClipping(resultState, algorithmState, ambientState);
-        updateSpeedBumpState(resultState, algorithmState, ambientState.getSpeedBumpIndex());
+        updateShelfState(resultState, algorithmState, ambientState);
         getNotificationChildrenStates(resultState, algorithmState);
     }
 
@@ -123,17 +119,20 @@
         }
     }
 
-    private void updateSpeedBumpState(StackScrollState resultState,
-            StackScrollAlgorithmState algorithmState, int speedBumpIndex) {
+    private void updateShelfState(StackScrollState resultState,
+            StackScrollAlgorithmState algorithmState, AmbientState ambientState) {
         int childCount = algorithmState.visibleChildren.size();
+        int shelfIndex = ambientState.getShelfIndex();
         for (int i = 0; i < childCount; i++) {
             View child = algorithmState.visibleChildren.get(i);
             ExpandableViewState childViewState = resultState.getViewStateForView(child);
 
             // The speed bump can also be gone, so equality needs to be taken when comparing
             // indices.
-            childViewState.belowSpeedBump = speedBumpIndex != -1 && i >= speedBumpIndex;
+            childViewState.belowShelf = shelfIndex != -1 && i >= shelfIndex;
         }
+        NotificationShelf shelf = ambientState.getShelf();
+        shelf.updateState(resultState, algorithmState, ambientState);
     }
 
     private void updateClipping(StackScrollState resultState,
@@ -201,7 +200,7 @@
             childViewState.hideSensitive = hideSensitive;
             boolean isActivatedChild = activatedChild == child;
             if (dimmed && isActivatedChild) {
-                childViewState.zTranslation += 2.0f * mZDistanceBetweenElements;
+                childViewState.zTranslation += 2.0f * ambientState.getZDistanceBetweenElements();
             }
         }
     }
@@ -263,6 +262,9 @@
         for (int i = 0; i < childCount; i++) {
             ExpandableView v = (ExpandableView) hostView.getChildAt(i);
             if (v.getVisibility() != View.GONE) {
+                if (v == ambientState.getShelf()) {
+                    continue;
+                }
                 notGoneIndex = updateNotGoneIndex(resultState, state, notGoneIndex, v);
                 float increasedPadding = v.getIncreasedPaddingAmount();
                 if (increasedPadding != 0.0f) {
@@ -345,11 +347,13 @@
         if (i == 0) {
             updateFirstChildHeight(child, childViewState, childHeight, ambientState);
         }
+        int shelfIndex = ambientState.getShelfIndex();
+        boolean belowShelf = shelfIndex != -1 && i >= shelfIndex;
 
         // The y position after this element
         float nextYPosition = currentYPosition + childHeight +
                 paddingAfterChild;
-        if (nextYPosition >= bottomStackStart) {
+        if (nextYPosition >= bottomStackStart && belowShelf) {
             // Case 1:
             // We are in the bottom stack.
             if (currentYPosition >= bottomStackStart) {
@@ -368,8 +372,12 @@
             // Case 2:
             // We are in the regular scroll area.
             childViewState.location = ExpandableViewState.LOCATION_MAIN_AREA;
-            clampPositionToBottomStackStart(childViewState, childViewState.height, childHeight,
-                    ambientState);
+            if (belowShelf) {
+                clampPositionToBottomStackStart(childViewState, childViewState.height, childHeight,
+                        ambientState);
+            } else {
+                clampPositionToShelf(i, childViewState, ambientState);
+            }
         }
 
         if (i == 0 && ambientState.getScrollY() <= 0) {
@@ -393,12 +401,7 @@
 
     protected int getPaddingAfterChild(StackScrollAlgorithmState algorithmState,
             ExpandableView child) {
-        Float paddingValue = algorithmState.increasedPaddingMap.get(child);
-        return paddingValue == null
-                ? mPaddingBetweenElements
-                : (int) NotificationUtils.interpolate(mPaddingBetweenElements,
-                        mIncreasedPaddingBetweenElements,
-                        paddingValue);
+        return algorithmState.getPaddingAfterChild(child);
     }
 
     private void updateHeadsUpStates(StackScrollState resultState,
@@ -485,6 +488,33 @@
         }
     }
 
+    /**
+     * Clamp the height of the child down such that its end is at most on the beginning of
+     * the shelf.
+     *
+     * @param index the index of the view
+     * @param childViewState the view state of the child
+     * @param ambientState the ambient state
+     */
+    private void clampPositionToShelf(int index, ExpandableViewState childViewState,
+            AmbientState ambientState) {
+        int minHeight = ambientState.getShelf().getNotificationMergeSize();
+        int shelfEnd = ambientState.getInnerHeight();
+        int shelfStart = shelfEnd - ambientState.getShelf().getIntrinsicHeight();
+        int maxChildEnd = shelfEnd;
+        if (index != ambientState.getShelfIndex() - 1) {
+            maxChildEnd = shelfStart - mPaddingBetweenElements;
+        }
+        childViewState.yTranslation = Math.min(childViewState.yTranslation, shelfStart);
+        if (childViewState.yTranslation + childViewState.height > maxChildEnd) {
+            float newHeight = maxChildEnd - childViewState.yTranslation;
+            if (newHeight < minHeight) {
+                newHeight = Math.min(minHeight, shelfEnd - childViewState.yTranslation);
+            }
+            childViewState.height = (int) newHeight;
+        }
+    }
+
     protected int getMaxAllowedChildHeight(View child) {
         if (child instanceof ExpandableView) {
             ExpandableView expandableView = (ExpandableView) child;
@@ -586,6 +616,8 @@
             AmbientState ambientState) {
         ExpandableView child = algorithmState.visibleChildren.get(i);
         ExpandableViewState childViewState = resultState.getViewStateForView(child);
+        int zDistanceBetweenElements = ambientState.getZDistanceBetweenElements();
+        float baseZ = ambientState.getBaseZHeight();
         if (i > (childCount - 1 - algorithmState.itemsInBottomStack)) {
             // We are in the bottom stack
             float numItemsAbove = i - (childCount - 1 - algorithmState.itemsInBottomStack);
@@ -599,13 +631,13 @@
                 } else {
                     zSubtraction = FakeShadowView.SHADOW_SIBLING_TRESHOLD
                             + (numItemsAbove - factor) * (1.0f / (1.0f - factor))
-                            * (mZDistanceBetweenElements
+                            * (zDistanceBetweenElements
                             - FakeShadowView.SHADOW_SIBLING_TRESHOLD);
                 }
             } else {
-                zSubtraction = numItemsAbove * mZDistanceBetweenElements;
+                zSubtraction = numItemsAbove * zDistanceBetweenElements;
             }
-            childViewState.zTranslation = mZBasicHeight - zSubtraction;
+            childViewState.zTranslation = baseZ - zSubtraction;
         } else if (child.mustStayOnScreen()
                 && childViewState.yTranslation < ambientState.getTopPadding()
                 + ambientState.getStackTranslation()) {
@@ -616,10 +648,10 @@
                         + ambientState.getStackTranslation() - childViewState.yTranslation;
                 childrenOnTop += Math.min(1.0f, overlap / childViewState.height);
             }
-            childViewState.zTranslation = mZBasicHeight
-                    + childrenOnTop * mZDistanceBetweenElements;
+            childViewState.zTranslation = baseZ
+                    + childrenOnTop * zDistanceBetweenElements;
         } else {
-            childViewState.zTranslation = mZBasicHeight;
+            childViewState.zTranslation = baseZ;
         }
     }
 
@@ -646,7 +678,7 @@
         this.mIsExpanded = isExpanded;
     }
 
-    protected class StackScrollAlgorithmState {
+    public class StackScrollAlgorithmState {
 
         /**
          * The scroll position of the algorithm
@@ -673,6 +705,15 @@
          * no increased padding, a value of 1 means full padding.
          */
         public final HashMap<ExpandableView, Float> increasedPaddingMap = new HashMap<>();
+
+        public int getPaddingAfterChild(ExpandableView child) {
+            Float paddingValue = increasedPaddingMap.get(child);
+            return paddingValue == null
+                    ? mPaddingBetweenElements
+                    : (int) NotificationUtils.interpolate(mPaddingBetweenElements,
+                            mIncreasedPaddingBetweenElements,
+                            paddingValue);
+        }
     }
 
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
index 1dc09f1..7cf9ec1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
@@ -79,7 +79,10 @@
         viewState.alpha = 1f;
         viewState.shadowAlpha = 1f;
         viewState.notGoneIndex = -1;
+        viewState.xTranslation = view.getTranslationX();
         viewState.hidden = false;
+        viewState.scaleX = view.getScaleX();
+        viewState.scaleY = view.getScaleY();
     }
 
     public ExpandableViewState getViewStateForView(View requestedView) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
index 0009a04..3ba7774 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
@@ -247,8 +247,8 @@
         // start dimmed animation
         child.setDimmed(viewState.dimmed, mAnimationFilter.animateDimmed);
 
-        // apply speed bump state
-        child.setBelowSpeedBump(viewState.belowSpeedBump);
+        // apply below shelf state
+        child.setBelowShelf(viewState.belowShelf);
 
         // start hiding sensitive animation
         child.setHideSensitive(viewState.hideSensitive, mAnimationFilter.animateHideSensitive,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
index 2fead7e..108f0e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
@@ -28,25 +28,34 @@
 public class ViewState {
 
     public float alpha;
+    public float xTranslation;
     public float yTranslation;
     public float zTranslation;
     public boolean gone;
     public boolean hidden;
+    public float scaleX = 1.0f;
+    public float scaleY = 1.0f;
 
     public void copyFrom(ViewState viewState) {
         alpha = viewState.alpha;
+        xTranslation = viewState.xTranslation;
         yTranslation = viewState.yTranslation;
         zTranslation = viewState.zTranslation;
         gone = viewState.gone;
         hidden = viewState.hidden;
+        scaleX = viewState.scaleX;
+        scaleY = viewState.scaleY;
     }
 
     public void initFrom(View view) {
         alpha = view.getAlpha();
+        xTranslation = view.getTranslationX();
         yTranslation = view.getTranslationY();
         zTranslation = view.getTranslationZ();
         gone = view.getVisibility() == View.GONE;
         hidden = false;
+        scaleX = view.getScaleX();
+        scaleY = view.getScaleY();
     }
 
     /**
@@ -57,17 +66,10 @@
             // don't do anything with it
             return;
         }
-        float alpha = view.getAlpha();
-        float yTranslation = view.getTranslationY();
-        float xTranslation = view.getTranslationX();
-        float zTranslation = view.getTranslationZ();
-        float newAlpha = this.alpha;
-        float newYTranslation = this.yTranslation;
-        float newZTranslation = this.zTranslation;
-        boolean becomesInvisible = newAlpha == 0.0f || this.hidden;
-        if (alpha != newAlpha && xTranslation == 0) {
+        boolean becomesInvisible = this.alpha == 0.0f || this.hidden;
+        if (view.getAlpha() != this.alpha) {
             // apply layer type
-            boolean becomesFullyVisible = newAlpha == 1.0f;
+            boolean becomesFullyVisible = this.alpha == 1.0f;
             boolean newLayerTypeIsHardware = !becomesInvisible && !becomesFullyVisible
                     && view.hasOverlappingRendering();
             int layerType = view.getLayerType();
@@ -79,7 +81,7 @@
             }
 
             // apply alpha
-            view.setAlpha(newAlpha);
+            view.setAlpha(this.alpha);
         }
 
         // apply visibility
@@ -92,14 +94,29 @@
             }
         }
 
+        // apply xTranslation
+        if (view.getTranslationX() != this.xTranslation) {
+            view.setTranslationX(this.xTranslation);
+        }
+
         // apply yTranslation
-        if (yTranslation != newYTranslation) {
-            view.setTranslationY(newYTranslation);
+        if (view.getTranslationY() != this.yTranslation) {
+            view.setTranslationY(this.yTranslation);
         }
 
         // apply zTranslation
-        if (zTranslation != newZTranslation) {
-            view.setTranslationZ(newZTranslation);
+        if (view.getTranslationZ() != this.zTranslation) {
+            view.setTranslationZ(this.zTranslation);
+        }
+
+        // apply scaleX
+        if (view.getScaleX() != this.scaleX) {
+            view.setScaleX(this.scaleX);
+        }
+
+        // apply scaleY
+        if (view.getScaleY() != this.scaleY) {
+            view.setScaleY(this.scaleY);
         }
     }
 }