Implemented visual speed-bump for notifications.

The separation between the important and the less important
notifications has now a visual representation.

Bug: 14607473
Change-Id: I8baa0a08924ec041be2884a2834139477313ab40
diff --git a/packages/SystemUI/res/layout/status_bar_notification_speed_bump.xml b/packages/SystemUI/res/layout/status_bar_notification_speed_bump.xml
new file mode 100644
index 0000000..ff8800c
--- /dev/null
+++ b/packages/SystemUI/res/layout/status_bar_notification_speed_bump.xml
@@ -0,0 +1,65 @@
+<!--
+  ~ 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.SpeedBumpView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:focusable="true"
+    android:clickable="true"
+    android:visibility="gone"
+    >
+    <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/speed_bump_height_collapsed"
+            android:layout_gravity="top"
+            android:orientation="horizontal">
+        <View
+            android:id="@+id/speedbump_line_left"
+            android:layout_width="0dp"
+            android:layout_height="1dp"
+            android:layout_weight="1"
+            android:background="#6fdddddd"
+            android:layout_gravity="center_vertical"/>
+        <com.android.systemui.statusbar.SpeedBumpDotsLayout
+                android:id="@+id/speed_bump_dots_layout"
+                android:layout_width="34dp"
+                android:layout_marginStart="8dp"
+                android:layout_marginEnd="8dp"
+                android:layout_height="match_parent"
+                android:layout_weight="0"/>
+        <View
+            android:id="@+id/speedbump_line_right"
+            android:layout_width="0dp"
+            android:layout_height="1dp"
+            android:layout_weight="1"
+            android:background="#6fdddddd"
+            android:layout_gravity="center_vertical"/>
+    </LinearLayout>
+    <TextView
+            android:id="@+id/speed_bump_text"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="top|center_horizontal"
+            android:fontFamily="sans-serif-condensed"
+            android:textSize="15sp"
+            android:singleLine="true"
+            android:textColor="#eeeeee"
+            android:visibility="invisible"
+            android:text="@string/speed_bump_explanation"
+            android:paddingTop="4dp" />
+</com.android.systemui.statusbar.SpeedBumpView>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 9582b21..cba13004 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -58,6 +58,18 @@
     <!-- Tint color for the content on the notification overflow card. -->
     <color name="keyguard_overflow_content_color">#ff666666</color>
 
+    <!-- The color of the red speed bump dot -->
+    <color name="speed_bump_dot_red">#ffd50000</color>
+
+    <!-- The color of the blue speed bump dot -->
+    <color name="speed_bump_dot_blue">#ff2962ff</color>
+
+    <!-- The color of the yellow speed bump dot -->
+    <color name="speed_bump_dot_yellow">#ffffd600</color>
+
+    <!-- The color of the green speed bump dot -->
+    <color name="speed_bump_dot_green">#ff00c853</color>
+
     <!-- The default recents task bar background color. -->
     <color name="recents_task_bar_default_background_color">#e6444444</color>
     <!-- The default recents task bar text color. -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 79612e0..d403bf3 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -260,6 +260,15 @@
     <!-- The padding between the individual notification cards. -->
     <dimen name="notification_padding">4dp</dimen>
 
+    <!-- The height of the collapsed speed bump view. -->
+    <dimen name="speed_bump_height_collapsed">24dp</dimen>
+
+    <!-- The padding inset the explanation text needs compared to the collapsed height -->
+    <dimen name="speed_bump_text_padding_inset">10dp</dimen>
+
+    <!-- The height of the speed bump dots. -->
+    <dimen name="speed_bump_dots_height">5dp</dimen>
+
     <!-- The total height of the stack in its collapsed size (i.e. when quick settings is open) -->
     <dimen name="collapsed_stack_height">94dp</dimen>
 
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index a50a0ac..b3829a3 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -553,6 +553,9 @@
         <item quantity="other">%d more</item>
     </plurals>
 
+    <!-- An explanation for the visual speed bump in the notifications, which will appear when you click on it. [CHAR LIMIT=50] -->
+    <string name="speed_bump_explanation">Less urgent notifications below</string>
+
     <!-- Shows to explain the double tap interaction with notifications: After tapping a notification on Keyguard, this will explain users to tap again to launch a notification. [CHAR LIMIT=60] -->
     <string name="notification_tap_again">Tap again to open</string>
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 6090948..c1228d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -877,6 +877,7 @@
         entry.row = row;
         entry.row.setHeightRange(mRowMinHeight, mRowMaxHeight);
         entry.row.setOnActivatedListener(this);
+        entry.row.setIsBelowSpeedBump(isBelowSpeedBump(entry.notification));
         entry.expanded = contentViewLocal;
         entry.expandedPublic = publicViewLocal;
         entry.setBigContentView(bigContentViewLocal);
@@ -1039,8 +1040,8 @@
         if (DEBUG) {
             Log.d(TAG, "addNotificationViews: added at " + pos);
         }
-        updateRowStates();
         updateNotificationIcons();
+        updateRowStates();
     }
 
     private void addNotificationViews(IBinder key, StatusBarNotification notification) {
@@ -1060,6 +1061,7 @@
         mKeyguardIconOverflowContainer.getIconsView().removeAllViews();
         int n = mNotificationData.size();
         int visibleNotifications = 0;
+        int speedBumpIndex = -1;
         boolean onKeyguard = mState == StatusBarState.KEYGUARD;
         for (int i = n-1; i >= 0; i--) {
             NotificationData.Entry entry = mNotificationData.get(i);
@@ -1087,6 +1089,10 @@
                 entry.row.setVisibility(View.VISIBLE);
                 visibleNotifications++;
             }
+            if (entry.row.getVisibility() != View.GONE && speedBumpIndex == -1
+                    && entry.row.isBelowSpeedBump() ) {
+                speedBumpIndex = n - 1 - i;
+            }
         }
 
         if (onKeyguard && mKeyguardIconOverflowContainer.getIconsView().getChildCount() > 0) {
@@ -1094,6 +1100,8 @@
         } else {
             mKeyguardIconOverflowContainer.setVisibility(View.GONE);
         }
+
+        mStackScroller.updateSpeedBumpIndex(speedBumpIndex);
     }
 
     private boolean shouldShowOnKeyguard(StatusBarNotification sbn) {
@@ -1309,9 +1317,19 @@
         } else {
             entry.row.setOnClickListener(null);
         }
+        boolean wasBelow = entry.row.isBelowSpeedBump();
+        boolean nowBelow = isBelowSpeedBump(notification);
+        if (wasBelow != nowBelow) {
+            entry.row.setIsBelowSpeedBump(nowBelow);
+        }
         entry.row.notifyContentUpdated();
     }
 
+    private boolean isBelowSpeedBump(StatusBarNotification notification) {
+        return notification.getNotification().priority ==
+                Notification.PRIORITY_MIN;
+    }
+
     protected void notifyHeadsUpScreenOn(boolean screenOn) {
         if (!screenOn && mInterruptingNotificationEntry != null) {
             mHandler.sendEmptyMessage(MSG_ESCALATE_HEADS_UP);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 39f2bb9..f6c80fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -52,6 +52,7 @@
     private NotificationContentView mPublicLayout;
     private NotificationContentView mPrivateLayout;
     private int mMaxExpandHeight;
+    private boolean mIsBelowSpeedBump;
 
     public ExpandableNotificationRow(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -244,6 +245,14 @@
         mPrivateLayout.setClipTopAmount(clipTopAmount);
     }
 
+    public boolean isBelowSpeedBump() {
+        return mIsBelowSpeedBump;
+    }
+
+    public void setIsBelowSpeedBump(boolean isBelow) {
+        this.mIsBelowSpeedBump = isBelow;
+    }
+
     public void notifyContentUpdated() {
         mPrivateLayout.notifyContentUpdated();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
index 4bd0e1c..061396d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
@@ -40,11 +40,15 @@
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
         if (!mActualHeightInitialized && mActualHeight == 0) {
-            mActualHeight = getHeight();
+            mActualHeight = getInitialHeight();
         }
         mActualHeightInitialized = true;
     }
 
+    protected int getInitialHeight() {
+        return getHeight();
+    }
+
     @Override
     public boolean dispatchTouchEvent(MotionEvent ev) {
         if (filterMotionEvent(ev)) {
@@ -146,6 +150,10 @@
         }
     }
 
+    public boolean isTransparent() {
+        return false;
+    }
+
     /**
      * A listener notifying when {@link #getActualHeight} changes.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpDotView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpDotView.java
new file mode 100644
index 0000000..3ca021a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpDotView.java
@@ -0,0 +1,47 @@
+/*
+ * 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.graphics.Canvas;
+import android.graphics.Outline;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.view.View;
+
+/**
+ * An single dot of the {@link com.android.systemui.statusbar.SpeedBumpDotsLayout}
+ */
+public class SpeedBumpDotView extends View {
+
+    private final Paint mPaint = new Paint();
+
+    public SpeedBumpDotView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mPaint.setAntiAlias(true);
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        float radius = getWidth() / 2.0f;
+        canvas.drawCircle(radius, radius, radius, mPaint);
+    }
+
+    public void setColor(int color) {
+        mPaint.setColor(color);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpDotsAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpDotsAlgorithm.java
new file mode 100644
index 0000000..cac6327
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpDotsAlgorithm.java
@@ -0,0 +1,80 @@
+/*
+ * 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.view.View;
+import com.android.systemui.R;
+
+/**
+ * The Algorithm of the {@link com.android.systemui.statusbar.SpeedBumpDotsLayout} which can be
+ * queried for {@link * com.android.systemui.statusbar.SpeedBumpDotsState}
+ */
+public class SpeedBumpDotsAlgorithm {
+
+    private final float mDotRadius;
+
+    public SpeedBumpDotsAlgorithm(Context context) {
+        mDotRadius = context.getResources().getDimensionPixelSize(R.dimen.speed_bump_dots_height)
+                / 2.0f;
+    }
+
+    public void getState(SpeedBumpDotsState resultState) {
+
+        // First reset the current state and ensure that every View has a ViewState
+        resultState.resetViewStates();
+
+        SpeedBumpDotsLayout hostView = resultState.getHostView();
+        boolean currentlyVisible = hostView.isCurrentlyVisible();
+        resultState.setActiveState(currentlyVisible
+                ? SpeedBumpDotsState.SHOWN
+                : SpeedBumpDotsState.HIDDEN);
+        int hostWidth = hostView.getWidth();
+        float layoutWidth = hostWidth - 2 * mDotRadius;
+        int childCount = hostView.getChildCount();
+        float paddingBetween = layoutWidth / (childCount - 1);
+        float centerY = hostView.getHeight() / 2.0f;
+        for (int i = 0; i < childCount; i++) {
+            View child = hostView.getChildAt(i);
+            SpeedBumpDotsState.ViewState viewState = resultState.getViewStateForView(child);
+            if (currentlyVisible) {
+                float xTranslation = i * paddingBetween;
+                viewState.xTranslation = xTranslation;
+                viewState.yTranslation = calculateYTranslation(hostView, centerY, xTranslation,
+                        layoutWidth);
+            } else {
+                viewState.xTranslation = layoutWidth / 2;
+                viewState.yTranslation = centerY - mDotRadius;
+            }
+            viewState.alpha = currentlyVisible ? 1.0f : 0.0f;
+            viewState.scale = currentlyVisible ? 1.0f : 0.5f;
+        }
+    }
+
+    private float calculateYTranslation(SpeedBumpDotsLayout hostView, float centerY,
+            float xTranslation, float layoutWidth) {
+        float t = hostView.getAnimationProgress();
+        if (t == 0.0f || t == 1.0f) {
+            return centerY - mDotRadius;
+        }
+        float damping = (0.5f -Math.abs(0.5f - t)) * 1.3f;
+        float partialOffset = xTranslation / layoutWidth;
+        float indentFactor = (float) (Math.sin((t + partialOffset * 1.5f) * - Math.PI) * damping);
+        return (1.0f - indentFactor) * centerY - mDotRadius;
+    }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpDotsLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpDotsLayout.java
new file mode 100644
index 0000000..ddf5215
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpDotsLayout.java
@@ -0,0 +1,136 @@
+/*
+ * 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.animation.TimeAnimator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import com.android.systemui.R;
+
+/**
+ * A layout with a certain number of dots which are integrated in the
+ * {@link com.android.systemui.statusbar.SpeedBumpView}
+ */
+public class SpeedBumpDotsLayout extends ViewGroup {
+
+    private static final float DOT_CLICK_ANIMATION_LENGTH = 300;
+    private final int mDotSize;
+    private final SpeedBumpDotsAlgorithm mAlgorithm = new SpeedBumpDotsAlgorithm(getContext());
+    private final SpeedBumpDotsState mCurrentState = new SpeedBumpDotsState(this);
+    private boolean mIsCurrentlyVisible = true;
+    private final ValueAnimator mClickAnimator;
+    private float mAnimationProgress;
+    private ValueAnimator.AnimatorUpdateListener mClickUpdateListener
+            = new ValueAnimator.AnimatorUpdateListener() {
+        @Override
+        public void onAnimationUpdate(ValueAnimator animation) {
+            mAnimationProgress = animation.getAnimatedFraction();
+            updateChildren();
+        }
+    };
+
+    public SpeedBumpDotsLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mDotSize = getResources().getDimensionPixelSize(R.dimen.speed_bump_dots_height);
+        createDots(context, attrs);
+        mClickAnimator = TimeAnimator.ofFloat(0, DOT_CLICK_ANIMATION_LENGTH);
+        mClickAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
+        mClickAnimator.addUpdateListener(mClickUpdateListener);
+    }
+
+    private void createDots(Context context, AttributeSet attrs) {
+        SpeedBumpDotView blueDot = new SpeedBumpDotView(context, attrs);
+        blueDot.setColor(getResources().getColor(R.color.speed_bump_dot_blue));
+        addView(blueDot);
+
+        SpeedBumpDotView redDot = new SpeedBumpDotView(context, attrs);
+        redDot.setColor(getResources().getColor(R.color.speed_bump_dot_red));
+        addView(redDot);
+
+        SpeedBumpDotView yellowDot = new SpeedBumpDotView(context, attrs);
+        yellowDot.setColor(getResources().getColor(R.color.speed_bump_dot_yellow));
+        addView(yellowDot);
+
+        SpeedBumpDotView greenDot = new SpeedBumpDotView(context, attrs);
+        greenDot.setColor(getResources().getColor(R.color.speed_bump_dot_green));
+        addView(greenDot);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        int childWidthSpec = MeasureSpec.makeMeasureSpec(mDotSize,
+                MeasureSpec.getMode(widthMeasureSpec));
+        int childHeightSpec = MeasureSpec.makeMeasureSpec(mDotSize,
+                MeasureSpec.getMode(heightMeasureSpec));
+        measureChildren(childWidthSpec, childHeightSpec);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            View child = getChildAt(i);
+            child.layout(0, 0, mDotSize, mDotSize);
+        }
+        if (changed) {
+            updateChildren();
+        }
+    }
+
+    private void updateChildren() {
+        mAlgorithm.getState(mCurrentState);
+        mCurrentState.apply();
+    }
+
+    public void performVisibilityAnimation(boolean visible) {
+        if (mClickAnimator.isRunning()) {
+            mClickAnimator.cancel();
+        }
+        mIsCurrentlyVisible = visible;
+        mAlgorithm.getState(mCurrentState);
+        mCurrentState.animateToState();
+    }
+
+    public void setInvisible() {
+        mIsCurrentlyVisible = false;
+        mAlgorithm.getState(mCurrentState);
+        mCurrentState.apply();
+    }
+
+    public boolean isCurrentlyVisible() {
+        return mIsCurrentlyVisible;
+    }
+
+    public void performDotClickAnimation() {
+        if (mClickAnimator.isRunning()) {
+            // don't perform an animation if it's running already
+            return;
+        }
+        mClickAnimator.start();
+    }
+
+
+    public float getAnimationProgress() {
+        return mAnimationProgress;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpDotsState.java b/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpDotsState.java
new file mode 100644
index 0000000..06a7f95
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpDotsState.java
@@ -0,0 +1,139 @@
+/*
+ * 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.view.View;
+import android.view.ViewPropertyAnimator;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A state of a {@link com.android.systemui.statusbar.SpeedBumpDotsLayout}
+ */
+public class SpeedBumpDotsState {
+
+    public static final int HIDDEN = 1;
+    public static final int SHOWN = 2;
+    private static final int VISIBILITY_ANIMATION_DELAY_PER_ELEMENT = 80;
+
+    private final SpeedBumpDotsLayout mHostView;
+    private final HashMap<View, ViewState> mStateMap = new HashMap<View, ViewState>();
+    private final Interpolator mFastOutSlowInInterpolator;
+    private int mActiveState = 0;
+
+    public SpeedBumpDotsState(SpeedBumpDotsLayout hostLayout) {
+        mHostView = hostLayout;
+        mFastOutSlowInInterpolator = AnimationUtils
+                .loadInterpolator(hostLayout.getContext(),
+                        android.R.interpolator.fast_out_slow_in);
+    }
+
+    public SpeedBumpDotsLayout getHostView() {
+        return mHostView;
+    }
+
+    public void resetViewStates() {
+        int numChildren = mHostView.getChildCount();
+        for (int i = 0; i < numChildren; i++) {
+            View child = mHostView.getChildAt(i);
+            ViewState viewState = mStateMap.get(child);
+            if (viewState == null) {
+                viewState = new ViewState();
+                mStateMap.put(child, viewState);
+            }
+        }
+    }
+
+    public ViewState getViewStateForView(View requestedView) {
+        return mStateMap.get(requestedView);
+    }
+
+    public void apply() {
+        int childCount = mHostView.getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            View child = mHostView.getChildAt(i);
+            ViewState viewState = mStateMap.get(child);
+            float translationX = child.getTranslationX();
+            float translationY = child.getTranslationY();
+            float scale = child.getScaleX();
+            float alpha = child.getAlpha();
+            if (translationX != viewState.xTranslation) {
+                child.setTranslationX(viewState.xTranslation);
+            }
+            if (translationY != viewState.yTranslation) {
+                child.setTranslationY(viewState.yTranslation);
+            }
+            if (scale != viewState.scale) {
+                child.setScaleX(viewState.scale);
+                child.setScaleY(viewState.scale);
+            }
+            if (alpha != viewState.alpha) {
+                child.setAlpha(viewState.alpha);
+            }
+        }
+    }
+
+    public void animateToState() {
+        int childCount = mHostView.getChildCount();
+        int middleIndex = (childCount - 1) / 2;
+        long delayPerElement = VISIBILITY_ANIMATION_DELAY_PER_ELEMENT;
+        boolean isAppearing = getActiveState() == SHOWN;
+        boolean isDisappearing = getActiveState() == HIDDEN;
+        for (int i = 0; i < childCount; i++) {
+            int delayIndex;
+            if (i <= middleIndex) {
+                delayIndex = i * 2;
+            } else {
+                int distToMiddle = i - middleIndex;
+                delayIndex = (childCount - 1) - (distToMiddle - 1) * 2;
+            }
+            long startDelay = 0;
+            if (isAppearing || isDisappearing) {
+                if (isDisappearing) {
+                    delayIndex = childCount - 1 - delayIndex;
+                }
+                startDelay = delayIndex * delayPerElement;
+            }
+            View child = mHostView.getChildAt(i);
+            ViewState viewState = mStateMap.get(child);
+            child.animate().setInterpolator(mFastOutSlowInInterpolator)
+                    .setStartDelay(startDelay)
+                    .alpha(viewState.alpha).withLayer()
+                    .translationX(viewState.xTranslation)
+                    .translationY(viewState.yTranslation)
+                    .scaleX(viewState.scale).scaleY(viewState.scale);
+        }
+    }
+
+    public int getActiveState() {
+        return mActiveState;
+    }
+
+    public void setActiveState(int mActiveState) {
+        this.mActiveState = mActiveState;
+    }
+
+    public static class ViewState {
+        float xTranslation;
+        float yTranslation;
+        float alpha;
+        float scale;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpView.java
new file mode 100644
index 0000000..8ae503a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpView.java
@@ -0,0 +1,265 @@
+/*
+ * 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.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.Outline;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+import android.widget.TextView;
+import com.android.systemui.R;
+
+/**
+ * The view representing the separation between important and less important notifications
+ */
+public class SpeedBumpView extends ExpandableView implements View.OnClickListener {
+
+    private final int mCollapsedHeight;
+    private final int mDotsHeight;
+    private final int mTextPaddingInset;
+    private SpeedBumpDotsLayout mDots;
+    private View mLineLeft;
+    private View mLineRight;
+    private boolean mIsExpanded;
+    private boolean mDividerVisible = true;
+    private ValueAnimator mCurrentAnimator;
+    private final Interpolator mFastOutSlowInInterpolator;
+    private float mCenterX;
+    private TextView mExplanationText;
+    private boolean mExplanationTextVisible = false;
+    private AnimatorListenerAdapter mHideExplanationListener = new AnimatorListenerAdapter() {
+        private boolean mCancelled;
+
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            if (!mCancelled) {
+                mExplanationText.setVisibility(View.INVISIBLE);
+            }
+        }
+
+        @Override
+        public void onAnimationCancel(Animator animation) {
+            mCancelled = true;
+        }
+
+        @Override
+        public void onAnimationStart(Animator animation) {
+            mCancelled = false;
+        }
+    };
+    private Animator.AnimatorListener mAnimationFinishedListener = new AnimatorListenerAdapter() {
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            mCurrentAnimator = null;
+        }
+    };
+
+    public SpeedBumpView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mCollapsedHeight = getResources()
+                .getDimensionPixelSize(R.dimen.speed_bump_height_collapsed);
+        mTextPaddingInset = getResources().getDimensionPixelSize(
+                R.dimen.speed_bump_text_padding_inset);
+        mDotsHeight = getResources().getDimensionPixelSize(R.dimen.speed_bump_dots_height);
+        setOnClickListener(this);
+        mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(getContext(),
+                android.R.interpolator.fast_out_slow_in);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mDots = (SpeedBumpDotsLayout) findViewById(R.id.speed_bump_dots_layout);
+        mLineLeft = findViewById(R.id.speedbump_line_left);
+        mLineRight = findViewById(R.id.speedbump_line_right);
+        mExplanationText = (TextView) findViewById(R.id.speed_bump_text);
+        resetExplanationText();
+
+    }
+
+    @Override
+    protected int getInitialHeight() {
+        return mCollapsedHeight;
+    }
+
+    @Override
+    public int getIntrinsicHeight() {
+        return getActualHeight();
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        Outline outline = new Outline();
+        mCenterX = getWidth() / 2;
+        float centerY = getHeight() / 2;
+        // TODO: hide outline better
+        // Temporary workaround to hide outline on a transparent view
+        int outlineLeft = (int) (mCenterX - getResources().getDisplayMetrics().densityDpi * 8);
+        int outlineTop = (int) (centerY - mDotsHeight / 2);
+        outline.setOval(outlineLeft, outlineTop, outlineLeft + mDotsHeight,
+                outlineTop + mDotsHeight);
+        setOutline(outline);
+        mLineLeft.setPivotX(mLineLeft.getWidth());
+        mLineLeft.setPivotY(mLineLeft.getHeight() / 2);
+        mLineRight.setPivotX(0);
+        mLineRight.setPivotY(mLineRight.getHeight() / 2);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        measureChildren(widthMeasureSpec, heightMeasureSpec);
+        int height = mCollapsedHeight + mExplanationText.getMeasuredHeight() - mTextPaddingInset;
+        setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), height);
+    }
+
+    @Override
+    public void onClick(View v) {
+        if (mCurrentAnimator != null) {
+            return;
+        }
+        int startValue = mIsExpanded ? getMaxHeight() : mCollapsedHeight;
+        int endValue = mIsExpanded ? mCollapsedHeight : getMaxHeight();
+        mCurrentAnimator = ValueAnimator.ofInt(startValue, endValue);
+        mCurrentAnimator.setInterpolator(mFastOutSlowInInterpolator);
+        mCurrentAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                setActualHeight((int) animation.getAnimatedValue());
+            }
+        });
+        mCurrentAnimator.addListener(mAnimationFinishedListener);
+        mCurrentAnimator.start();
+        mIsExpanded = !mIsExpanded;
+        mDots.performDotClickAnimation();
+        animateExplanationTextInternal(mIsExpanded);
+    }
+
+    private void animateExplanationTextInternal(boolean visible) {
+        if (mExplanationTextVisible != visible) {
+            float translationY = 0.0f;
+            float scale = 0.5f;
+            float alpha = 0.0f;
+            boolean needsHideListener = true;
+            if (visible) {
+                mExplanationText.setVisibility(VISIBLE);
+                translationY = mDots.getBottom() - mTextPaddingInset;
+                scale = 1.0f;
+                alpha = 1.0f;
+                needsHideListener = false;
+            }
+            mExplanationText.animate().setInterpolator(mFastOutSlowInInterpolator)
+                    .alpha(alpha)
+                    .scaleX(scale)
+                    .scaleY(scale)
+                    .translationY(translationY)
+                    .setListener(needsHideListener ? mHideExplanationListener : null)
+                    .withLayer();
+            mExplanationTextVisible = visible;
+        }
+    }
+
+    @Override
+    public boolean isTransparent() {
+        return true;
+    }
+
+    public void performVisibilityAnimation(boolean nowVisible) {
+        animateDivider(nowVisible);
+
+        // Animate explanation Text
+        if (mIsExpanded) {
+            animateExplanationTextInternal(nowVisible);
+        }
+    }
+
+    public void animateDivider(boolean nowVisible) {
+        if (nowVisible != mDividerVisible) {
+            // Animate dividers
+            float endValue = nowVisible ? 1.0f : 0.0f;
+            float endTranslationXLeft = nowVisible ? 0.0f : mCenterX - mLineLeft.getRight();
+            float endTranslationXRight = nowVisible ? 0.0f : mCenterX - mLineRight.getLeft();
+            mLineLeft.animate()
+                    .alpha(endValue)
+                    .withLayer()
+                    .scaleX(endValue)
+                    .scaleY(endValue)
+                    .translationX(endTranslationXLeft)
+                    .setInterpolator(mFastOutSlowInInterpolator);
+            mLineRight.animate()
+                    .alpha(endValue)
+                    .withLayer()
+                    .scaleX(endValue)
+                    .scaleY(endValue)
+                    .translationX(endTranslationXRight)
+                    .setInterpolator(mFastOutSlowInInterpolator);
+
+            // Animate dots
+            mDots.performVisibilityAnimation(nowVisible);
+            mDividerVisible = nowVisible;
+        }
+    }
+
+    public void setInvisible() {
+        float endTranslationXLeft = mCenterX - mLineLeft.getRight();
+        float endTranslationXRight = mCenterX - mLineRight.getLeft();
+        mLineLeft.setAlpha(0.0f);
+        mLineLeft.setScaleX(0.0f);
+        mLineLeft.setScaleY(0.0f);
+        mLineLeft.setTranslationX(endTranslationXLeft);
+        mLineRight.setAlpha(0.0f);
+        mLineRight.setScaleX(0.0f);
+        mLineRight.setScaleY(0.0f);
+        mLineRight.setTranslationX(endTranslationXRight);
+        mDots.setInvisible();
+        resetExplanationText();
+
+        mDividerVisible = false;
+    }
+
+    public void collapse() {
+        if (mIsExpanded) {
+            setActualHeight(mCollapsedHeight);
+            mIsExpanded = false;
+        }
+        resetExplanationText();
+    }
+
+    public void animateExplanationText(boolean nowVisible) {
+        if (mIsExpanded) {
+            animateExplanationTextInternal(nowVisible);
+        }
+    }
+
+    private void resetExplanationText() {
+        mExplanationText.setTranslationY(0);
+        mExplanationText.setVisibility(INVISIBLE);
+        mExplanationText.setAlpha(0.0f);
+        mExplanationText.setScaleX(0.5f);
+        mExplanationText.setScaleY(0.5f);
+        mExplanationTextVisible = false;
+    }
+
+    public boolean isExpanded() {
+        return mIsExpanded;
+    }
+}
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 b9f5ab2..cd985f5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -143,8 +143,9 @@
         }
     }
 
-    public void animateNextTopPaddingChange() {
+    public void animateToFullShade() {
         mAnimateNextTopPaddingChange = true;
+        mNotificationStackScroller.goToFullShade();
         requestLayout();
     }
 
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 d3b5f96..4e1ffa5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -106,6 +106,7 @@
 import com.android.systemui.statusbar.NotificationData.Entry;
 import com.android.systemui.statusbar.NotificationOverflowContainer;
 import com.android.systemui.statusbar.SignalClusterView;
+import com.android.systemui.statusbar.SpeedBumpView;
 import com.android.systemui.statusbar.StatusBarIconView;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -634,6 +635,10 @@
         mKeyguardIconOverflowContainer.setOnClickListener(mOverflowClickListener);
         mStackScroller.addView(mKeyguardIconOverflowContainer);
 
+        SpeedBumpView speedBump = (SpeedBumpView) LayoutInflater.from(mContext).inflate(
+                        R.layout.status_bar_notification_speed_bump, mStackScroller, false);
+        mStackScroller.setSpeedBumpView(speedBump);
+
         mExpandedContents = mStackScroller;
 
         mHeader = (StatusBarHeaderView) mStatusBarWindow.findViewById(R.id.header);
@@ -1150,7 +1155,7 @@
         ArrayList<View> toRemove = new ArrayList<View>();
         for (int i=0; i< mStackScroller.getChildCount(); i++) {
             View child = mStackScroller.getChildAt(i);
-            if (!toShow.contains(child) && child != mKeyguardIconOverflowContainer) {
+            if (!toShow.contains(child) && child instanceof ExpandableNotificationRow) {
                 toRemove.add(child);
             }
         }
@@ -2722,7 +2727,7 @@
         setBarState(StatusBarState.SHADE);
         if (mLeaveOpenOnKeyguardHide) {
             mLeaveOpenOnKeyguardHide = false;
-            mNotificationPanel.animateNextTopPaddingChange();
+            mNotificationPanel.animateToFullShade();
         } else {
             instantCollapseNotificationPanel();
         }
@@ -2894,7 +2899,7 @@
             mLeaveOpenOnKeyguardHide = true;
             showBouncer();
         } else {
-            mNotificationPanel.animateNextTopPaddingChange();
+            mNotificationPanel.animateToFullShade();
             setBarState(StatusBarState.SHADE_LOCKED);
             updateKeyguardState();
         }
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 deab757..b21e12c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
@@ -30,6 +30,7 @@
     private View mActivatedChild;
     private float mOverScrollTopAmount;
     private float mOverScrollBottomAmount;
+    private int mSpeedBumpIndex = -1;
 
     public int getScrollY() {
         return mScrollY;
@@ -86,4 +87,12 @@
     public float getOverScrollAmount(boolean top) {
         return top ? mOverScrollTopAmount : mOverScrollBottomAmount;
     }
+
+    public int getSpeedBumpIndex() {
+        return mSpeedBumpIndex;
+    }
+
+    public void setSpeedBumpIndex(int speedBumpIndex) {
+        mSpeedBumpIndex = speedBumpIndex;
+    }
 }
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 fbb6695..f125c9a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -39,6 +39,7 @@
 import com.android.systemui.SwipeHelper;
 import com.android.systemui.statusbar.ExpandableNotificationRow;
 import com.android.systemui.statusbar.ExpandableView;
+import com.android.systemui.statusbar.SpeedBumpView;
 import com.android.systemui.statusbar.stack.StackScrollState.ViewState;
 import com.android.systemui.statusbar.policy.ScrollAdapter;
 
@@ -126,6 +127,8 @@
     private boolean mActivateNeedsAnimation;
     private boolean mIsExpanded = true;
     private boolean mChildrenUpdateRequested;
+    private SpeedBumpView mSpeedBumpView;
+    private boolean mIsExpansionChanging;
     private ViewTreeObserver.OnPreDrawListener mChildrenUpdater
             = new ViewTreeObserver.OnPreDrawListener() {
         @Override
@@ -244,6 +247,22 @@
         requestChildrenUpdate();
     }
 
+    public void updateSpeedBumpIndex(int newIndex) {
+        int currentIndex = indexOfChild(mSpeedBumpView);
+
+        // If we are currently layouted before the new speed bump index, we have to decrease it.
+        boolean validIndex = newIndex > 0;
+        if (newIndex > getChildCount() - 1) {
+            validIndex = false;
+            newIndex = -1;
+        }
+        if (validIndex && currentIndex != newIndex) {
+            changeViewPosition(mSpeedBumpView, newIndex);
+        }
+        updateSpeedBump(validIndex);
+        mAmbientState.setSpeedBumpIndex(newIndex);
+    }
+
     public void setChildLocationsChangedListener(OnChildLocationsChangedListener listener) {
         mListener = listener;
     }
@@ -1044,6 +1063,10 @@
         mCurrentStackScrollState.removeViewStateForView(child);
         mStackScrollAlgorithm.notifyChildrenChanged(this);
         updateScrollStateForRemovedChild(child);
+        generateRemoveAnimation(child);
+    }
+
+    private void generateRemoveAnimation(View child) {
         if (mIsExpanded) {
 
             if (!mChildrenToAddAnimated.contains(child)) {
@@ -1120,7 +1143,9 @@
      */
     public void changeViewPosition(View child, int newIndex) {
         if (child != null && child.getParent() == this) {
-            // TODO: handle this
+            removeView(child);
+            addView(child, newIndex);
+            // TODO: handle events
         }
     }
 
@@ -1362,10 +1387,12 @@
     }
 
     public void onExpansionStarted() {
+        mIsExpansionChanging = true;
         mStackScrollAlgorithm.onExpansionStarted(mCurrentStackScrollState);
     }
 
     public void onExpansionStopped() {
+        mIsExpansionChanging = false;
         mStackScrollAlgorithm.onExpansionStopped();
     }
 
@@ -1374,6 +1401,7 @@
         mStackScrollAlgorithm.setIsExpanded(isExpanded);
         if (!isExpanded) {
             mOwnScrollY = 0;
+            mSpeedBumpView.collapse();
         }
     }
 
@@ -1432,6 +1460,34 @@
         }
     }
 
+    public void setSpeedBumpView(SpeedBumpView speedBumpView) {
+        mSpeedBumpView = speedBumpView;
+        addView(speedBumpView);
+    }
+
+    private void updateSpeedBump(boolean visible) {
+        int newVisibility = visible ? VISIBLE : GONE;
+        int oldVisibility = mSpeedBumpView.getVisibility();
+        if (newVisibility != oldVisibility) {
+            mSpeedBumpView.setVisibility(newVisibility);
+            if (visible) {
+                mSpeedBumpView.collapse();
+                // Make invisible to ensure that the appear animation is played.
+                mSpeedBumpView.setInvisible();
+                if (!mIsExpansionChanging) {
+                    generateAddAnimation(mSpeedBumpView);
+                }
+            } else {
+                mSpeedBumpView.performVisibilityAnimation(false);
+                generateRemoveAnimation(mSpeedBumpView);
+            }
+        }
+    }
+
+    public void goToFullShade() {
+        updateSpeedBump(true);
+    }
+
     /**
      * A listener that is notified when some child locations might have changed.
      */
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 8fc26d8..011411c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
@@ -23,6 +23,7 @@
 import android.view.ViewGroup;
 
 import com.android.systemui.statusbar.ExpandableView;
+import com.android.systemui.statusbar.SpeedBumpView;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -167,13 +168,50 @@
                         clipHeight,
                         (int) (newHeight - (previousNotificationStart - newYTranslation)));
 
-                previousNotificationStart = newYTranslation + child.getClipTopAmount();
-                previousNotificationEnd = newNotificationEnd;
-                previousNotificationIsSwiped = child.getTranslationX() != 0;
+                if (!child.isTransparent()) {
+                    // Only update the previous values if we are not transparent,
+                    // otherwise we would clip to a transparent view.
+                    previousNotificationStart = newYTranslation + child.getClipTopAmount();
+                    previousNotificationEnd = newNotificationEnd;
+                    previousNotificationIsSwiped = child.getTranslationX() != 0;
+                }
+
+                if(child instanceof SpeedBumpView) {
+                    performSpeedBumpAnimation(i, (SpeedBumpView) child, newNotificationEnd,
+                            newYTranslation);
+                }
             }
         }
     }
 
+    private void performSpeedBumpAnimation(int i, SpeedBumpView speedBump, float speedBumpEnd,
+            float speedBumpStart) {
+        View nextChild = getNextChildNotGone(i);
+        if (nextChild != null) {
+            ViewState nextState = getViewStateForView(nextChild);
+            boolean startIsAboveNext = nextState.yTranslation > speedBumpStart;
+            speedBump.animateDivider(startIsAboveNext);
+
+            // handle expanded case
+            if (speedBump.isExpanded()) {
+                boolean endIsAboveNext = nextState.yTranslation > speedBumpEnd;
+                speedBump.animateExplanationText(endIsAboveNext);
+            }
+
+        }
+    }
+
+    private View getNextChildNotGone(int childIndex) {
+        int childCount = mHostView.getChildCount();
+        for (int i = childIndex + 1; i < childCount; i++) {
+            View child = mHostView.getChildAt(i);
+            if (child.getVisibility() != View.GONE) {
+                return child;
+            }
+        }
+        return null;
+    }
+
     /**
      * Updates the shadow outline and the clipping for a view.
      *