Implemented new camera affordance

Also fixed a bug in the touch logic where you could close the shade / hint
after going to the camera / phone.

Bug: 15126905
Change-Id: Iadfde56cb68f4048868eedec6bd3456f55823cd9
diff --git a/packages/SystemUI/res/drawable/ic_chevron_left.xml b/packages/SystemUI/res/drawable/ic_chevron_left.xml
new file mode 100644
index 0000000..27c2034
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_chevron_left.xml
@@ -0,0 +1,28 @@
+<!--
+  ~ 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
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+    <size
+        android:width="24dp"
+        android:height="24dp"/>
+
+    <viewport
+        android:viewportWidth="36.0"
+        android:viewportHeight="36.0"/>
+
+    <path
+        android:fill="#ffffffff"
+        android:pathData="M23.1,11.1l-2.1000004,-2.1000004 -9.0,9.0 9.0,9.0 2.1000004,-2.1000004 -6.8999996,-6.8999996z"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
index db5983b..42fb740 100644
--- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml
+++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
@@ -22,7 +22,7 @@
     android:layout_height="match_parent"
     android:layout_width="match_parent"
     >
-    <com.android.systemui.statusbar.AlphaImageView
+    <com.android.systemui.statusbar.KeyguardAffordanceView
         android:id="@+id/camera_button"
         android:layout_height="64dp"
         android:layout_width="64dp"
@@ -32,7 +32,7 @@
         android:scaleType="center"
         android:contentDescription="@string/accessibility_camera_button" />
 
-    <com.android.systemui.statusbar.AlphaImageView
+    <com.android.systemui.statusbar.KeyguardAffordanceView
         android:id="@+id/phone_button"
         android:layout_height="64dp"
         android:layout_width="64dp"
@@ -52,7 +52,7 @@
         android:textColor="#ffffff"
         android:textAppearance="?android:attr/textAppearanceSmall"/>
 
-    <com.android.systemui.statusbar.AlphaImageView
+    <com.android.systemui.statusbar.KeyguardAffordanceView
         android:id="@+id/lock_icon"
         android:layout_width="64dp"
         android:layout_height="64dp"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 9bc2b0d..4e536e4 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -295,6 +295,15 @@
     <!-- The minimum amount the user needs to swipe to go to the camera / phone. -->
     <dimen name="keyguard_min_swipe_amount">75dp</dimen>
 
+    <!-- The minimum background radius when swiping to a side for the camera / phone affordances. -->
+    <dimen name="keyguard_affordance_min_background_radius">30dp</dimen>
+
+    <!-- The grow amount for the camera and phone circles when hinting -->
+    <dimen name="hint_grow_amount_sideways">60dp</dimen>
+
+    <!-- The chevron padding to the circle when hinting -->
+    <dimen name="hint_chevron_circle_padding">16dp</dimen>
+
     <!-- Volume panel dialog y offset -->
     <dimen name="volume_panel_top">0dp</dimen>
 
@@ -317,9 +326,6 @@
     <!-- Move distance for the unlock hint animation on the lockscreen -->
     <dimen name="hint_move_distance">75dp</dimen>
 
-    <!-- Move distance for the other hint animations on the lockscreen (phone, camera)-->
-    <dimen name="hint_move_distance_sideways">60dp</dimen>
-
     <!-- The width of the region on the left/right edge of the screen for performing the camera/
          phone hints. -->
     <dimen name="edge_tap_area_width">48dp</dimen>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 0a288d9..225b6af 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -244,9 +244,6 @@
                             // the user switches to home.  We know it is safe to do at this
                             // point, so make sure new activity switches are now allowed.
                             ActivityManagerNative.getDefault().resumeAppSwitches();
-                            // Also, notifications can be launched from the lock screen,
-                            // so dismiss the lock screen when the activity starts.
-                            ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity();
                         } catch (RemoteException e) {
                         }
 
@@ -1153,9 +1150,6 @@
                         // the user switches to home.  We know it is safe to do at this
                         // point, so make sure new activity switches are now allowed.
                         ActivityManagerNative.getDefault().resumeAppSwitches();
-                        // Also, notifications can be launched from the lock screen,
-                        // so dismiss the lock screen when the activity starts.
-                        ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity();
                     } catch (RemoteException e) {
                     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java
new file mode 100644
index 0000000..845e0ae
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java
@@ -0,0 +1,429 @@
+/*
+ * 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.ArgbEvaluator;
+import android.animation.PropertyValuesHolder;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+import android.widget.ImageView;
+import com.android.systemui.R;
+
+/**
+ * An ImageView which does not have overlapping renderings commands and therefore does not need a
+ * layer when alpha is changed.
+ */
+public class KeyguardAffordanceView extends ImageView {
+
+    private static final long CIRCLE_APPEAR_DURATION = 80;
+    private static final long CIRCLE_DISAPPEAR_MAX_DURATION = 200;
+    private static final long NORMAL_ANIMATION_DURATION = 200;
+    public static final float MAX_ICON_SCALE_AMOUNT = 1.5f;
+    public static final float MIN_ICON_SCALE_AMOUNT = 0.8f;
+
+    private final int mMinBackgroundRadius;
+    private final Paint mCirclePaint;
+    private final Interpolator mAppearInterpolator;
+    private final Interpolator mDisappearInterpolator;
+    private final int mInverseColor;
+    private final int mNormalColor;
+    private final ArgbEvaluator mColorInterpolator;
+    private final FlingAnimationUtils mFlingAnimationUtils;
+    private final Drawable mArrowDrawable;
+    private final int mHintChevronPadding;
+    private float mCircleRadius;
+    private int mCenterX;
+    private int mCenterY;
+    private ValueAnimator mCircleAnimator;
+    private ValueAnimator mAlphaAnimator;
+    private ValueAnimator mScaleAnimator;
+    private ValueAnimator mArrowAnimator;
+    private float mCircleStartValue;
+    private boolean mCircleWillBeHidden;
+    private int[] mTempPoint = new int[2];
+    private float mImageScale;
+    private int mCircleColor;
+    private boolean mIsLeft;
+    private float mArrowAlpha = 0.0f;
+    private AnimatorListenerAdapter mCircleEndListener = new AnimatorListenerAdapter() {
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            mCircleAnimator = null;
+        }
+    };
+    private AnimatorListenerAdapter mScaleEndListener = new AnimatorListenerAdapter() {
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            mScaleAnimator = null;
+        }
+    };
+    private AnimatorListenerAdapter mAlphaEndListener = new AnimatorListenerAdapter() {
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            mAlphaAnimator = null;
+        }
+    };
+    private AnimatorListenerAdapter mArrowEndListener = new AnimatorListenerAdapter() {
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            mArrowAnimator = null;
+        }
+    };
+
+    public KeyguardAffordanceView(Context context) {
+        this(context, null);
+    }
+
+    public KeyguardAffordanceView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public KeyguardAffordanceView(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public KeyguardAffordanceView(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        mCirclePaint = new Paint();
+        mCirclePaint.setAntiAlias(true);
+        mCircleColor = 0xffffffff;
+        mCirclePaint.setColor(mCircleColor);
+
+        mNormalColor = 0xffffffff;
+        mInverseColor = 0xff000000;
+        mMinBackgroundRadius = mContext.getResources().getDimensionPixelSize(
+                R.dimen.keyguard_affordance_min_background_radius);
+        mHintChevronPadding = mContext.getResources().getDimensionPixelSize(
+                R.dimen.hint_chevron_circle_padding);
+        mAppearInterpolator = AnimationUtils.loadInterpolator(mContext,
+                android.R.interpolator.linear_out_slow_in);
+        mDisappearInterpolator = AnimationUtils.loadInterpolator(mContext,
+                android.R.interpolator.fast_out_linear_in);
+        mColorInterpolator = new ArgbEvaluator();
+        mFlingAnimationUtils = new FlingAnimationUtils(mContext, 0.3f);
+        mArrowDrawable = context.getDrawable(R.drawable.ic_chevron_left);
+        mArrowDrawable.setBounds(0, 0, mArrowDrawable.getIntrinsicWidth(),
+                mArrowDrawable.getIntrinsicHeight());
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        mCenterX = getWidth() / 2;
+        mCenterY = getHeight() / 2;
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        drawBackgroundCircle(canvas);
+        drawArrow(canvas);
+        canvas.save();
+        updateIconColor();
+        canvas.scale(mImageScale, mImageScale, getWidth() / 2, getHeight() / 2);
+        super.onDraw(canvas);
+        canvas.restore();
+    }
+
+    private void drawArrow(Canvas canvas) {
+        if (mArrowAlpha > 0) {
+            canvas.save();
+            canvas.translate(mCenterX, mCenterY);
+            if (mIsLeft) {
+                canvas.scale(-1.0f, 1.0f);
+            }
+            canvas.translate(- mCircleRadius - mHintChevronPadding
+                    - mArrowDrawable.getIntrinsicWidth() / 2,
+                    - mArrowDrawable.getIntrinsicHeight() / 2);
+            mArrowDrawable.setAlpha((int) (mArrowAlpha * 255));
+            mArrowDrawable.draw(canvas);
+            canvas.restore();
+        }
+    }
+
+    private void updateIconColor() {
+        Drawable drawable = getDrawable().mutate();
+        float alpha = mCircleRadius / mMinBackgroundRadius;
+        alpha = Math.min(1.0f, alpha);
+        int color = (int) mColorInterpolator.evaluate(alpha, mNormalColor, mInverseColor);
+        drawable.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
+    }
+
+    private void drawBackgroundCircle(Canvas canvas) {
+        if (mCircleRadius > 0) {
+            updateCircleColor();
+            canvas.drawCircle(mCenterX, mCenterY, mCircleRadius, mCirclePaint);
+        }
+    }
+
+    private void updateCircleColor() {
+        float fraction = 0.5f + 0.5f * Math.max(0.0f, Math.min(1.0f,
+                (mCircleRadius - mMinBackgroundRadius) / (0.5f * mMinBackgroundRadius)));
+        int color = Color.argb((int) (Color.alpha(mCircleColor) * fraction),
+                Color.red(mCircleColor),
+                Color.green(mCircleColor), Color.blue(mCircleColor));
+        mCirclePaint.setColor(color);
+    }
+
+    public void finishAnimation(float velocity, final Runnable mAnimationEndRunnable) {
+        cancelAnimator(mCircleAnimator);
+        float maxCircleSize = getMaxCircleSize();
+        ValueAnimator animatorToRadius = getAnimatorToRadius(maxCircleSize);
+        mFlingAnimationUtils.applyDismissing(animatorToRadius, mCircleRadius, maxCircleSize,
+                velocity, maxCircleSize);
+        animatorToRadius.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mAnimationEndRunnable.run();
+            }
+        });
+        animatorToRadius.start();
+        setImageAlpha(0, true);
+    }
+
+    private float getMaxCircleSize() {
+        getLocationInWindow(mTempPoint);
+        float rootWidth = getRootView().getWidth();
+        float width = mTempPoint[0] + mCenterX;
+        width = Math.max(rootWidth - width, width);
+        float height = mTempPoint[1] + mCenterY;
+        return (float) Math.hypot(width, height);
+    }
+
+    public void setCircleRadius(float circleRadius) {
+        setCircleRadius(circleRadius, false);
+    }
+
+    public void setCircleRadiusWithoutAnimation(float circleRadius) {
+        cancelAnimator(mCircleAnimator);
+        setCircleRadius(circleRadius, true);
+    }
+
+    private void setCircleRadius(float circleRadius, boolean noAnimation) {
+
+        // Check if we need a new animation
+        boolean radiusHidden = (mCircleAnimator != null && mCircleWillBeHidden)
+                || (mCircleAnimator == null && mCircleRadius == 0.0f);
+        boolean nowHidden = circleRadius == 0.0f;
+        boolean radiusNeedsAnimation = (radiusHidden != nowHidden) && !noAnimation;
+        if (!radiusNeedsAnimation) {
+            if (mCircleAnimator == null) {
+                mCircleRadius = circleRadius;
+                invalidate();
+            } else if (!mCircleWillBeHidden) {
+
+                // We just update the end value
+                float diff = circleRadius - mMinBackgroundRadius;
+                PropertyValuesHolder[] values = mCircleAnimator.getValues();
+                values[0].setFloatValues(mCircleStartValue + diff, circleRadius);
+                mCircleAnimator.setCurrentPlayTime(mCircleAnimator.getCurrentPlayTime());
+            }
+        } else {
+            cancelAnimator(mCircleAnimator);
+            ValueAnimator animator = getAnimatorToRadius(circleRadius);
+            Interpolator interpolator = circleRadius == 0.0f
+                    ? mDisappearInterpolator
+                    : mAppearInterpolator;
+            animator.setInterpolator(interpolator);
+            float durationFactor = Math.abs(mCircleRadius - circleRadius)
+                    / (float) mMinBackgroundRadius;
+            long duration = (long) (CIRCLE_APPEAR_DURATION * durationFactor);
+            duration = Math.min(duration, CIRCLE_DISAPPEAR_MAX_DURATION);
+            animator.setDuration(duration);
+            animator.start();
+        }
+    }
+
+    private ValueAnimator getAnimatorToRadius(float circleRadius) {
+        ValueAnimator animator = ValueAnimator.ofFloat(mCircleRadius, circleRadius);
+        mCircleAnimator = animator;
+        mCircleStartValue = mCircleRadius;
+        mCircleWillBeHidden = circleRadius == 0.0f;
+        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                mCircleRadius = (float) animation.getAnimatedValue();
+                invalidate();
+            }
+        });
+        animator.addListener(mCircleEndListener);
+        return animator;
+    }
+
+    private void cancelAnimator(Animator animator) {
+        if (animator != null) {
+            animator.cancel();
+        }
+    }
+
+    public void setImageScale(float imageScale, boolean animate) {
+        setImageScale(imageScale, animate, -1, null);
+    }
+
+    /**
+     * Sets the scale of the containing image
+     *
+     * @param imageScale The new Scale.
+     * @param animate Should an animation be performed
+     * @param duration If animate, whats the duration? When -1 we take the default duration
+     * @param interpolator If animate, whats the interpolator? When null we take the default
+     *                     interpolator.
+     */
+    public void setImageScale(float imageScale, boolean animate, long duration,
+            Interpolator interpolator) {
+        cancelAnimator(mScaleAnimator);
+        if (!animate) {
+            mImageScale = imageScale;
+            invalidate();
+        } else {
+            ValueAnimator animator = ValueAnimator.ofFloat(mImageScale, imageScale);
+            mScaleAnimator = animator;
+            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+                @Override
+                public void onAnimationUpdate(ValueAnimator animation) {
+                    mImageScale = (float) animation.getAnimatedValue();
+                    invalidate();
+                }
+            });
+            animator.addListener(mScaleEndListener);
+            if (interpolator == null) {
+                interpolator = imageScale == 0.0f
+                        ? mDisappearInterpolator
+                        : mAppearInterpolator;
+            }
+            animator.setInterpolator(interpolator);
+            if (duration == -1) {
+                float durationFactor = Math.abs(mImageScale - imageScale)
+                        / (1.0f - MIN_ICON_SCALE_AMOUNT);
+                durationFactor = Math.min(1.0f, durationFactor);
+                duration = (long) (NORMAL_ANIMATION_DURATION * durationFactor);
+            }
+            animator.setDuration(duration);
+            animator.start();
+        }
+    }
+
+    public void setImageAlpha(float alpha, boolean animate) {
+        setImageAlpha(alpha, animate, -1, null, null);
+    }
+
+    /**
+     * Sets the alpha of the containing image
+     *
+     * @param alpha The new alpha.
+     * @param animate Should an animation be performed
+     * @param duration If animate, whats the duration? When -1 we take the default duration
+     * @param interpolator If animate, whats the interpolator? When null we take the default
+     *                     interpolator.
+     */
+    public void setImageAlpha(float alpha, boolean animate, long duration,
+            Interpolator interpolator, Runnable runnable) {
+        cancelAnimator(mAlphaAnimator);
+        int endAlpha = (int) (alpha * 255);
+        if (!animate) {
+            setImageAlpha(endAlpha);
+        } else {
+            int currentAlpha = getImageAlpha();
+            ValueAnimator animator = ValueAnimator.ofInt(currentAlpha, endAlpha);
+            mAlphaAnimator = animator;
+            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+                @Override
+                public void onAnimationUpdate(ValueAnimator animation) {
+                    setImageAlpha((int) animation.getAnimatedValue());
+                }
+            });
+            animator.addListener(mAlphaEndListener);
+            if (interpolator == null) {
+                interpolator = alpha == 0.0f
+                        ? mDisappearInterpolator
+                        : mAppearInterpolator;
+            }
+            animator.setInterpolator(interpolator);
+            if (duration == -1) {
+                float durationFactor = Math.abs(currentAlpha - endAlpha) / 255f;
+                durationFactor = Math.min(1.0f, durationFactor);
+                duration = (long) (NORMAL_ANIMATION_DURATION * durationFactor);
+            }
+            animator.setDuration(duration);
+            if (runnable != null) {
+                animator.addListener(getEndListener(runnable));
+            }
+            animator.start();
+        }
+    }
+
+    private Animator.AnimatorListener getEndListener(final Runnable runnable) {
+        return new AnimatorListenerAdapter() {
+            boolean mCancelled;
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                mCancelled = true;
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                if (!mCancelled) {
+                    runnable.run();
+                }
+            }
+        };
+    }
+
+    public float getCircleRadius() {
+        return mCircleRadius;
+    }
+
+    public void showArrow(boolean show) {
+        cancelAnimator(mArrowAnimator);
+        float targetAlpha = show ? 1.0f : 0.0f;
+        if (mArrowAlpha == targetAlpha) {
+            return;
+        }
+        ValueAnimator animator = ValueAnimator.ofFloat(mArrowAlpha, targetAlpha);
+        mArrowAnimator = animator;
+        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                mArrowAlpha = (float) animation.getAnimatedValue();
+                invalidate();
+            }
+        });
+        animator.addListener(mArrowEndListener);
+        Interpolator interpolator = show
+                    ? mAppearInterpolator
+                    : mDisappearInterpolator;
+        animator.setInterpolator(interpolator);
+        float durationFactor = Math.abs(mArrowAlpha - targetAlpha);
+        long duration = (long) (NORMAL_ANIMATION_DURATION * durationFactor);
+        animator.setDuration(duration);
+        animator.start();
+    }
+
+    public void setIsLeft(boolean left) {
+        mIsLeft = left;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
new file mode 100644
index 0000000..a8a0cb1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
@@ -0,0 +1,479 @@
+/*
+ * 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.phone;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.FlingAnimationUtils;
+import com.android.systemui.statusbar.KeyguardAffordanceView;
+
+/**
+ * A touch handler of the keyguard which is responsible for launching phone and camera affordances.
+ */
+public class KeyguardAffordanceHelper {
+
+    public static final float SWIPE_RESTING_ALPHA_AMOUNT = 0.5f;
+    public static final long HINT_PHASE1_DURATION = 200;
+    private static final long HINT_PHASE2_DURATION = 350;
+    private static final float BACKGROUND_RADIUS_SCALE_FACTOR = 0.15f;
+    private static final int HINT_CIRCLE_OPEN_DURATION = 500;
+
+    private final Context mContext;
+
+    private FlingAnimationUtils mFlingAnimationUtils;
+    private Callback mCallback;
+    private int mTrackingPointer;
+    private VelocityTracker mVelocityTracker;
+    private boolean mSwipingInProgress;
+    private float mInitialTouchX;
+    private float mInitialTouchY;
+    private float mTranslation;
+    private float mTranslationOnDown;
+    private int mTouchSlop;
+    private int mMinTranslationAmount;
+    private int mMinFlingVelocity;
+    private int mHintGrowAmount;
+    private final KeyguardAffordanceView mLeftIcon;
+    private final KeyguardAffordanceView mCenterIcon;
+    private final KeyguardAffordanceView mRightIcon;
+    private Interpolator mAppearInterpolator;
+    private Interpolator mDisappearInterpolator;
+    private Animator mSwipeAnimator;
+    private int mMinBackgroundRadius;
+    private boolean mMotionPerformedByUser;
+    private PowerManager mPM;
+    private AnimatorListenerAdapter mFlingEndListener = new AnimatorListenerAdapter() {
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            mSwipeAnimator = null;
+            setSwipingInProgress(false);
+        }
+    };
+    private Runnable mAnimationEndRunnable = new Runnable() {
+        @Override
+        public void run() {
+            mCallback.onAnimationToSideEnded();
+        }
+    };
+
+    KeyguardAffordanceHelper(Callback callback, Context context) {
+        mContext = context;
+        mCallback = callback;
+        mLeftIcon = mCallback.getLeftIcon();
+        mLeftIcon.setIsLeft(true);
+        mCenterIcon = mCallback.getCenterIcon();
+        mRightIcon = mCallback.getRightIcon();
+        updateIcon(mLeftIcon, 0.0f, SWIPE_RESTING_ALPHA_AMOUNT, false);
+        updateIcon(mCenterIcon, 0.0f, SWIPE_RESTING_ALPHA_AMOUNT, false);
+        updateIcon(mRightIcon, 0.0f, SWIPE_RESTING_ALPHA_AMOUNT, false);
+        initDimens();
+        mPM = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+    }
+
+    private void initDimens() {
+        final ViewConfiguration configuration = ViewConfiguration.get(mContext);
+        mTouchSlop = configuration.getScaledTouchSlop();
+        mMinFlingVelocity = configuration.getScaledMinimumFlingVelocity();
+        mMinTranslationAmount = mContext.getResources().getDimensionPixelSize(
+                R.dimen.keyguard_min_swipe_amount);
+        mMinBackgroundRadius = mContext.getResources().getDimensionPixelSize(
+                R.dimen.keyguard_affordance_min_background_radius);
+        mHintGrowAmount =
+                mContext.getResources().getDimensionPixelSize(R.dimen.hint_grow_amount_sideways);
+        mFlingAnimationUtils = new FlingAnimationUtils(mContext, 0.4f);
+        mAppearInterpolator = AnimationUtils.loadInterpolator(mContext,
+                android.R.interpolator.linear_out_slow_in);
+        mDisappearInterpolator = AnimationUtils.loadInterpolator(mContext,
+                android.R.interpolator.fast_out_linear_in);
+    }
+
+    public boolean onTouchEvent(MotionEvent event) {
+        int pointerIndex = event.findPointerIndex(mTrackingPointer);
+        if (pointerIndex < 0) {
+            pointerIndex = 0;
+            mTrackingPointer = event.getPointerId(pointerIndex);
+        }
+        final float y = event.getY(pointerIndex);
+        final float x = event.getX(pointerIndex);
+
+        boolean isUp = false;
+        switch (event.getActionMasked()) {
+            case MotionEvent.ACTION_DOWN:
+                if (mSwipingInProgress) {
+                    cancelAnimation();
+                }
+                mInitialTouchY = y;
+                mInitialTouchX = x;
+                mTranslationOnDown = mTranslation;
+                initVelocityTracker();
+                trackMovement(event);
+                mMotionPerformedByUser = false;
+                break;
+
+            case MotionEvent.ACTION_POINTER_UP:
+                final int upPointer = event.getPointerId(event.getActionIndex());
+                if (mTrackingPointer == upPointer) {
+                    // gesture is ongoing, find a new pointer to track
+                    final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
+                    final float newY = event.getY(newIndex);
+                    final float newX = event.getX(newIndex);
+                    mTrackingPointer = event.getPointerId(newIndex);
+                    mInitialTouchY = newY;
+                    mInitialTouchX = newX;
+                    mTranslationOnDown = mTranslation;
+                }
+                break;
+
+            case MotionEvent.ACTION_MOVE:
+                final float w = x - mInitialTouchX;
+                trackMovement(event);
+                if (((leftSwipePossible() && w > mTouchSlop)
+                        || (rightSwipePossible() && w < -mTouchSlop))
+                        && Math.abs(w) > Math.abs(y - mInitialTouchY)
+                        && !mSwipingInProgress) {
+                    cancelAnimation();
+                    mInitialTouchY = y;
+                    mInitialTouchX = x;
+                    mTranslationOnDown = mTranslation;
+                    setSwipingInProgress(true);
+                }
+                if (mSwipingInProgress) {
+                    setTranslation(mTranslationOnDown + x - mInitialTouchX, false, false);
+                }
+                break;
+
+            case MotionEvent.ACTION_UP:
+                isUp = true;
+            case MotionEvent.ACTION_CANCEL:
+                mTrackingPointer = -1;
+                trackMovement(event);
+                if (mSwipingInProgress) {
+                    flingWithCurrentVelocity(!isUp);
+                }
+                if (mVelocityTracker != null) {
+                    mVelocityTracker.recycle();
+                    mVelocityTracker = null;
+                }
+                break;
+        }
+        return true;
+    }
+
+    private void setSwipingInProgress(boolean inProgress) {
+        mSwipingInProgress = inProgress;
+        if (inProgress) {
+            mCallback.onSwipingStarted();
+        }
+    }
+
+    private boolean rightSwipePossible() {
+        return mRightIcon.getVisibility() == View.VISIBLE;
+    }
+
+    private boolean leftSwipePossible() {
+        return mLeftIcon.getVisibility() == View.VISIBLE;
+    }
+
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        return false;
+    }
+
+    public void startHintAnimation(boolean right, Runnable onFinishedListener) {
+
+        startHintAnimationPhase1(right, onFinishedListener);
+    }
+
+    private void startHintAnimationPhase1(final boolean right, final Runnable onFinishedListener) {
+        final KeyguardAffordanceView targetView = right ? mRightIcon : mLeftIcon;
+        targetView.showArrow(true);
+        ValueAnimator animator = getAnimatorToRadius(right, mHintGrowAmount);
+        animator.addListener(new AnimatorListenerAdapter() {
+            private boolean mCancelled;
+
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                mCancelled = true;
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                if (mCancelled) {
+                    mSwipeAnimator = null;
+                    onFinishedListener.run();
+                    targetView.showArrow(false);
+                } else {
+                    startUnlockHintAnimationPhase2(right, onFinishedListener);
+                }
+            }
+        });
+        animator.setInterpolator(mAppearInterpolator);
+        animator.setDuration(HINT_PHASE1_DURATION);
+        animator.start();
+        mSwipeAnimator = animator;
+    }
+
+    /**
+     * Phase 2: Move back.
+     */
+    private void startUnlockHintAnimationPhase2(boolean right, final Runnable onFinishedListener) {
+        final KeyguardAffordanceView targetView = right ? mRightIcon : mLeftIcon;
+        ValueAnimator animator = getAnimatorToRadius(right, 0);
+        animator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mSwipeAnimator = null;
+                targetView.showArrow(false);
+                onFinishedListener.run();
+            }
+
+            @Override
+            public void onAnimationStart(Animator animation) {
+                targetView.showArrow(false);
+            }
+        });
+        animator.setInterpolator(mDisappearInterpolator);
+        animator.setDuration(HINT_PHASE2_DURATION);
+        animator.setStartDelay(HINT_CIRCLE_OPEN_DURATION);
+        animator.start();
+        mSwipeAnimator = animator;
+    }
+
+    private ValueAnimator getAnimatorToRadius(final boolean right, int radius) {
+        final KeyguardAffordanceView targetView = right ? mRightIcon : mLeftIcon;
+        ValueAnimator animator = ValueAnimator.ofFloat(targetView.getCircleRadius(), radius);
+        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                float newRadius = (float) animation.getAnimatedValue();
+                targetView.setCircleRadiusWithoutAnimation(newRadius);
+                float translation = getTranslationFromRadius(newRadius);
+                mTranslation = right ? -translation : translation;
+                updateIconsFromRadius(targetView, newRadius);
+            }
+        });
+        return animator;
+    }
+
+    private void cancelAnimation() {
+        if (mSwipeAnimator != null) {
+            mSwipeAnimator.cancel();
+        }
+    }
+
+    private void flingWithCurrentVelocity(boolean forceSnapBack) {
+        float vel = getCurrentVelocity();
+
+        // We snap back if the current translation is not far enough
+        boolean snapBack = Math.abs(mTranslation) < Math.abs(mTranslationOnDown)
+                + mMinTranslationAmount;
+
+        // or if the velocity is in the opposite direction.
+        boolean velIsInWrongDirection = vel * mTranslation < 0;
+        snapBack |= Math.abs(vel) > mMinFlingVelocity && velIsInWrongDirection;
+        vel = snapBack ^ velIsInWrongDirection ? 0 : vel;
+        fling(vel, snapBack || forceSnapBack);
+    }
+
+    private void fling(float vel, final boolean snapBack) {
+        float target = mTranslation < 0 ? -mCallback.getPageWidth() : mCallback.getPageWidth();
+        target = snapBack ? 0 : target;
+
+        ValueAnimator animator = ValueAnimator.ofFloat(mTranslation, target);
+        mFlingAnimationUtils.apply(animator, mTranslation, target, vel);
+        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                mTranslation = (float) animation.getAnimatedValue();
+            }
+        });
+        animator.addListener(mFlingEndListener);
+        if (!snapBack) {
+            startFinishingCircleAnimation(vel * 0.375f, mAnimationEndRunnable);
+            mCallback.onAnimationToSideStarted(mTranslation < 0);
+        } else {
+            reset(true);
+        }
+        animator.start();
+        mSwipeAnimator = animator;
+    }
+
+    private void startFinishingCircleAnimation(float velocity, Runnable mAnimationEndRunnable) {
+        KeyguardAffordanceView targetView = mTranslation > 0 ? mLeftIcon : mRightIcon;
+        targetView.finishAnimation(velocity, mAnimationEndRunnable);
+    }
+
+    private void setTranslation(float translation, boolean isReset, boolean animateReset) {
+        translation = rightSwipePossible() ? translation : Math.max(0, translation);
+        translation = leftSwipePossible() ? translation : Math.min(0, translation);
+        float absTranslation = Math.abs(translation);
+        if (absTranslation > Math.abs(mTranslationOnDown) + mMinTranslationAmount ||
+                mMotionPerformedByUser) {
+            userActivity();
+            mMotionPerformedByUser = true;
+        }
+        if (translation != mTranslation || isReset) {
+            KeyguardAffordanceView targetView = translation > 0 ? mLeftIcon : mRightIcon;
+            KeyguardAffordanceView otherView = translation > 0 ? mRightIcon : mLeftIcon;
+            float alpha = absTranslation / mMinTranslationAmount;
+
+            // We interpolate the alpha of the other icons to 0
+            float fadeOutAlpha = SWIPE_RESTING_ALPHA_AMOUNT * (1.0f - alpha);
+            fadeOutAlpha = Math.max(0.0f, fadeOutAlpha);
+
+            // We interpolate the alpha of the targetView to 1
+            alpha = fadeOutAlpha + alpha;
+
+            boolean animateIcons = isReset && animateReset;
+            float radius = getRadiusFromTranslation(absTranslation);
+            if (!isReset) {
+                updateIcon(targetView, radius, alpha, false);
+            } else {
+                updateIcon(targetView, 0.0f, fadeOutAlpha, animateIcons);
+            }
+            updateIcon(otherView, 0.0f, fadeOutAlpha, animateIcons);
+            updateIcon(mCenterIcon, 0.0f, fadeOutAlpha, animateIcons);
+
+            mTranslation = translation;
+        }
+    }
+
+    private void updateIconsFromRadius(KeyguardAffordanceView targetView, float newRadius) {
+        float alpha = newRadius / mMinBackgroundRadius;
+
+        // We interpolate the alpha of the other icons to 0
+        float fadeOutAlpha = SWIPE_RESTING_ALPHA_AMOUNT * (1.0f - alpha);
+        fadeOutAlpha = Math.max(0.0f, fadeOutAlpha);
+
+        // We interpolate the alpha of the targetView to 1
+        alpha = fadeOutAlpha + alpha;
+        KeyguardAffordanceView otherView = targetView == mRightIcon ? mLeftIcon : mRightIcon;
+        updateIconAlpha(targetView, alpha, false);
+        updateIconAlpha(otherView, fadeOutAlpha, false);
+        updateIconAlpha(mCenterIcon, fadeOutAlpha, false);
+    }
+
+    private float getTranslationFromRadius(float circleSize) {
+        float translation = (circleSize - mMinBackgroundRadius) / BACKGROUND_RADIUS_SCALE_FACTOR;
+        return Math.max(0, translation);
+    }
+
+    private float getRadiusFromTranslation(float translation) {
+        return translation * BACKGROUND_RADIUS_SCALE_FACTOR + mMinBackgroundRadius;
+    }
+
+
+    private void userActivity() {
+        mPM.userActivity(SystemClock.uptimeMillis(), false);
+    }
+
+    public void animateHideLeftRightIcon() {
+        updateIcon(mRightIcon, 0f, 0f, true);
+        updateIcon(mLeftIcon, 0f, 0f, true);
+    }
+
+    private void updateIcon(KeyguardAffordanceView view, float circleRadius, float alpha,
+            boolean animate) {
+        if (view.getVisibility() != View.VISIBLE) {
+            return;
+        }
+        view.setCircleRadius(circleRadius);
+        updateIconAlpha(view, alpha, animate);
+    }
+
+    private void updateIconAlpha(KeyguardAffordanceView view, float alpha, boolean animate) {
+        float scale = getScale(alpha);
+        alpha = Math.min(1.0f, alpha);
+        view.setImageAlpha(alpha, animate);
+        view.setImageScale(scale, animate);
+    }
+
+    private float getScale(float alpha) {
+        float scale = alpha / SWIPE_RESTING_ALPHA_AMOUNT * 0.2f +
+                KeyguardAffordanceView.MIN_ICON_SCALE_AMOUNT;
+        return Math.min(scale, KeyguardAffordanceView.MAX_ICON_SCALE_AMOUNT);
+    }
+
+    private void trackMovement(MotionEvent event) {
+        if (mVelocityTracker != null) {
+            mVelocityTracker.addMovement(event);
+        }
+    }
+
+    private void initVelocityTracker() {
+        if (mVelocityTracker != null) {
+            mVelocityTracker.recycle();
+        }
+        mVelocityTracker = VelocityTracker.obtain();
+    }
+
+    private float getCurrentVelocity() {
+        if (mVelocityTracker == null) {
+            return 0;
+        }
+        mVelocityTracker.computeCurrentVelocity(1000);
+        return mVelocityTracker.getXVelocity();
+    }
+
+    public void onConfigurationChanged() {
+        initDimens();
+    }
+
+    public void reset(boolean animate) {
+        if (mSwipeAnimator != null) {
+            mSwipeAnimator.cancel();
+        }
+        setTranslation(0.0f, true, animate);
+        setSwipingInProgress(false);
+    }
+
+    public interface Callback {
+
+        /**
+         * Notifies the callback when an animation to a side page was started.
+         *
+         * @param rightPage Is the page animated to the right page?
+         */
+        void onAnimationToSideStarted(boolean rightPage);
+
+        /**
+         * Notifies the callback the animation to a side page has ended.
+         */
+        void onAnimationToSideEnded();
+
+        float getPageWidth();
+
+        void onSwipingStarted();
+
+        KeyguardAffordanceView getLeftIcon();
+
+        KeyguardAffordanceView getCenterIcon();
+
+        KeyguardAffordanceView getRightIcon();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index 74bc698..b9f012c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -39,6 +39,7 @@
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.policy.FlashlightController;
+import com.android.systemui.statusbar.KeyguardAffordanceView;
 
 /**
  * Implementation for the bottom area of the Keyguard, including camera/phone affordance and status
@@ -56,9 +57,9 @@
             new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
     private static final Intent PHONE_INTENT = new Intent(Intent.ACTION_DIAL);
 
-    private ImageView mCameraImageView;
-    private ImageView mPhoneImageView;
-    private ImageView mLockIcon;
+    private KeyguardAffordanceView mCameraImageView;
+    private KeyguardAffordanceView mPhoneImageView;
+    private KeyguardAffordanceView mLockIcon;
     private View mIndicationText;
 
     private ActivityStarter mActivityStarter;
@@ -87,9 +88,9 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
         mLockPatternUtils = new LockPatternUtils(mContext);
-        mCameraImageView = (ImageView) findViewById(R.id.camera_button);
-        mPhoneImageView = (ImageView) findViewById(R.id.phone_button);
-        mLockIcon = (ImageView) findViewById(R.id.lock_icon);
+        mCameraImageView = (KeyguardAffordanceView) findViewById(R.id.camera_button);
+        mPhoneImageView = (KeyguardAffordanceView) findViewById(R.id.phone_button);
+        mLockIcon = (KeyguardAffordanceView) findViewById(R.id.lock_icon);
         mIndicationText = findViewById(R.id.keyguard_indication_text);
         watchForCameraPolicyChanges();
         watchForAccessibilityChanges();
@@ -98,6 +99,8 @@
         mUnlockMethodCache = UnlockMethodCache.getInstance(getContext());
         mUnlockMethodCache.addListener(this);
         updateTrust();
+        setClipChildren(false);
+        setClipToPadding(false);
     }
 
     public void setActivityStarter(ActivityStarter activityStarter) {
@@ -228,15 +231,15 @@
         mLockIcon.setImageResource(iconRes);
     }
 
-    public ImageView getPhoneImageView() {
+    public KeyguardAffordanceView getPhoneView() {
         return mPhoneImageView;
     }
 
-    public ImageView getCameraImageView() {
+    public KeyguardAffordanceView getCameraView() {
         return mCameraImageView;
     }
 
-    public ImageView getLockIcon() {
+    public KeyguardAffordanceView getLockIcon() {
         return mLockIcon;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPageSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPageSwipeHelper.java
deleted file mode 100644
index d5f9619..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPageSwipeHelper.java
+++ /dev/null
@@ -1,496 +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.phone;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.os.PowerManager;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewPropertyAnimator;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
-
-import com.android.systemui.R;
-import com.android.systemui.statusbar.FlingAnimationUtils;
-
-import java.util.ArrayList;
-
-/**
- * A touch handler of the Keyguard which is responsible for swiping the content left or right.
- */
-public class KeyguardPageSwipeHelper {
-
-    private static final float SWIPE_MAX_ICON_SCALE_AMOUNT = 2.0f;
-    public static final float SWIPE_RESTING_ALPHA_AMOUNT = 0.5f;
-    public static final long HINT_PHASE1_DURATION = 250;
-    private static final long HINT_PHASE2_DURATION = 450;
-
-    private final Context mContext;
-
-    private FlingAnimationUtils mFlingAnimationUtils;
-    private Callback mCallback;
-    private int mTrackingPointer;
-    private VelocityTracker mVelocityTracker;
-    private boolean mSwipingInProgress;
-    private float mInitialTouchX;
-    private float mInitialTouchY;
-    private float mTranslation;
-    private float mTranslationOnDown;
-    private int mTouchSlop;
-    private int mMinTranslationAmount;
-    private int mMinFlingVelocity;
-    private int mHintDistance;
-    private final View mLeftIcon;
-    private final View mCenterIcon;
-    private final View mRightIcon;
-    private Interpolator mFastOutSlowIn;
-    private Interpolator mBounceInterpolator;
-    private Animator mSwipeAnimator;
-    private boolean mCallbackCalled;
-
-    KeyguardPageSwipeHelper(Callback callback, Context context) {
-        mContext = context;
-        mCallback = callback;
-        mLeftIcon = mCallback.getLeftIcon();
-        mCenterIcon = mCallback.getCenterIcon();
-        mRightIcon = mCallback.getRightIcon();
-        updateIcon(mLeftIcon, 1.0f, SWIPE_RESTING_ALPHA_AMOUNT, false);
-        updateIcon(mCenterIcon, 1.0f, SWIPE_RESTING_ALPHA_AMOUNT, false);
-        updateIcon(mRightIcon, 1.0f, SWIPE_RESTING_ALPHA_AMOUNT, false);
-        initDimens();
-    }
-
-    private void initDimens() {
-        final ViewConfiguration configuration = ViewConfiguration.get(mContext);
-        mTouchSlop = configuration.getScaledTouchSlop();
-        mMinFlingVelocity = configuration.getScaledMinimumFlingVelocity();
-        mMinTranslationAmount = mContext.getResources().getDimensionPixelSize(
-                R.dimen.keyguard_min_swipe_amount);
-        mHintDistance =
-                mContext.getResources().getDimensionPixelSize(R.dimen.hint_move_distance_sideways);
-        mFlingAnimationUtils = new FlingAnimationUtils(mContext, 0.4f);
-        mFastOutSlowIn = AnimationUtils.loadInterpolator(mContext,
-                android.R.interpolator.fast_out_slow_in);
-        mBounceInterpolator = new BounceInterpolator();
-    }
-
-    public boolean onTouchEvent(MotionEvent event) {
-        int pointerIndex = event.findPointerIndex(mTrackingPointer);
-        if (pointerIndex < 0) {
-            pointerIndex = 0;
-            mTrackingPointer = event.getPointerId(pointerIndex);
-        }
-        final float y = event.getY(pointerIndex);
-        final float x = event.getX(pointerIndex);
-
-        switch (event.getActionMasked()) {
-            case MotionEvent.ACTION_DOWN:
-                if (mSwipingInProgress) {
-                    cancelAnimations();
-                }
-                mInitialTouchY = y;
-                mInitialTouchX = x;
-                mTranslationOnDown = mTranslation;
-                initVelocityTracker();
-                trackMovement(event);
-                break;
-
-            case MotionEvent.ACTION_POINTER_UP:
-                final int upPointer = event.getPointerId(event.getActionIndex());
-                if (mTrackingPointer == upPointer) {
-                    // gesture is ongoing, find a new pointer to track
-                    final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
-                    final float newY = event.getY(newIndex);
-                    final float newX = event.getX(newIndex);
-                    mTrackingPointer = event.getPointerId(newIndex);
-                    mInitialTouchY = newY;
-                    mInitialTouchX = newX;
-                    mTranslationOnDown = mTranslation;
-                }
-                break;
-
-            case MotionEvent.ACTION_MOVE:
-                final float w = x - mInitialTouchX;
-                trackMovement(event);
-                if (((leftSwipePossible() && w > mTouchSlop)
-                        || (rightSwipePossible() && w < -mTouchSlop))
-                        && Math.abs(w) > Math.abs(y - mInitialTouchY)
-                        && !mSwipingInProgress) {
-                    cancelAnimations();
-                    mInitialTouchY = y;
-                    mInitialTouchX = x;
-                    mTranslationOnDown = mTranslation;
-                    mSwipingInProgress = true;
-                }
-                if (mSwipingInProgress) {
-                    setTranslation(mTranslationOnDown + x - mInitialTouchX, false);
-                }
-                break;
-
-            case MotionEvent.ACTION_UP:
-            case MotionEvent.ACTION_CANCEL:
-                mTrackingPointer = -1;
-                trackMovement(event);
-                if (mSwipingInProgress) {
-                    flingWithCurrentVelocity();
-                }
-                if (mVelocityTracker != null) {
-                    mVelocityTracker.recycle();
-                    mVelocityTracker = null;
-                }
-                break;
-        }
-        return true;
-    }
-
-    private boolean rightSwipePossible() {
-        return mRightIcon.getVisibility() == View.VISIBLE;
-    }
-
-    private boolean leftSwipePossible() {
-        return mLeftIcon.getVisibility() == View.VISIBLE;
-    }
-
-    public boolean onInterceptTouchEvent(MotionEvent ev) {
-        return false;
-    }
-
-    public void startHintAnimation(boolean right, Runnable onFinishedListener) {
-        startHintAnimationPhase1(right, onFinishedListener);
-    }
-
-    /**
-     * Phase 1: Move everything sidewards.
-     */
-    private void startHintAnimationPhase1(boolean right, final Runnable onFinishedListener) {
-        float target = right ? -mHintDistance : mHintDistance;
-        startHintTranslationAnimations(target, HINT_PHASE1_DURATION, mFastOutSlowIn);
-        ValueAnimator animator = ValueAnimator.ofFloat(mTranslation, target);
-        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
-                mTranslation = (float) animation.getAnimatedValue();
-            }
-        });
-        animator.addListener(new AnimatorListenerAdapter() {
-            private boolean mCancelled;
-
-            @Override
-            public void onAnimationCancel(Animator animation) {
-                mCancelled = true;
-            }
-
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                if (mCancelled) {
-                    mSwipeAnimator = null;
-                    onFinishedListener.run();
-                } else {
-                    startUnlockHintAnimationPhase2(onFinishedListener);
-                }
-            }
-        });
-        animator.setInterpolator(mFastOutSlowIn);
-        animator.setDuration(HINT_PHASE1_DURATION);
-        animator.start();
-        mSwipeAnimator = animator;
-    }
-
-    /**
-     * Phase 2: Move back.
-     */
-    private void startUnlockHintAnimationPhase2(final Runnable onFinishedListener) {
-        startHintTranslationAnimations(0f /* target */, HINT_PHASE2_DURATION, mBounceInterpolator);
-        ValueAnimator animator = ValueAnimator.ofFloat(mTranslation, 0f);
-        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
-                mTranslation = (float) animation.getAnimatedValue();
-            }
-        });
-        animator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mSwipeAnimator = null;
-                onFinishedListener.run();
-            }
-        });
-        animator.setInterpolator(mBounceInterpolator);
-        animator.setDuration(HINT_PHASE2_DURATION);
-        animator.start();
-        mSwipeAnimator = animator;
-    }
-
-    private void startHintTranslationAnimations(float target, long duration,
-            Interpolator interpolator) {
-        ArrayList<View> targetViews = mCallback.getTranslationViews();
-        for (View targetView : targetViews) {
-            targetView.animate()
-                    .setDuration(duration)
-                    .setInterpolator(interpolator)
-                    .translationX(target);
-        }
-    }
-
-    private void cancelAnimations() {
-        ArrayList<View> targetViews = mCallback.getTranslationViews();
-        for (View target : targetViews) {
-            target.animate().cancel();
-        }
-        View targetView = mTranslation > 0 ? mLeftIcon : mRightIcon;
-        targetView.animate().cancel();
-        if (mSwipeAnimator != null) {
-            mSwipeAnimator.cancel();
-            hideInactiveIcons(true);
-        }
-    }
-
-    private void flingWithCurrentVelocity() {
-        float vel = getCurrentVelocity();
-
-        // We snap back if the current translation is not far enough
-        boolean snapBack = Math.abs(mTranslation) < mMinTranslationAmount;
-
-        // or if the velocity is in the opposite direction.
-        boolean velIsInWrongDirection = vel * mTranslation < 0;
-        snapBack |= Math.abs(vel) > mMinFlingVelocity && velIsInWrongDirection;
-        vel = snapBack ^ velIsInWrongDirection ? 0 : vel;
-        fling(vel, snapBack);
-    }
-
-    private void fling(float vel, final boolean snapBack) {
-        float target = mTranslation < 0 ? -mCallback.getPageWidth() : mCallback.getPageWidth();
-        target = snapBack ? 0 : target;
-
-        // translation Animation
-        startTranslationAnimations(vel, target);
-
-        // animate left / right icon
-        startIconAnimation(vel, snapBack, target);
-
-        ValueAnimator animator = ValueAnimator.ofFloat(mTranslation, target);
-        mFlingAnimationUtils.apply(animator, mTranslation, target, vel);
-        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
-                mTranslation = (float) animation.getAnimatedValue();
-            }
-        });
-        animator.addListener(new AnimatorListenerAdapter() {
-            private boolean mCancelled;
-
-            @Override
-            public void onAnimationCancel(Animator animation) {
-                mCancelled = true;
-            }
-
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mSwipeAnimator = null;
-                mSwipingInProgress = false;
-                if (!snapBack && !mCallbackCalled && !mCancelled) {
-
-                    // ensure that the callback is called eventually
-                    mCallback.onAnimationToSideStarted(mTranslation < 0);
-                    mCallbackCalled = true;
-                }
-            }
-        });
-        if (!snapBack) {
-            mCallbackCalled = false;
-            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-                int frameNumber;
-
-                @Override
-                public void onAnimationUpdate(ValueAnimator animation) {
-                    if (frameNumber == 2 && !mCallbackCalled) {
-
-                        // we have to wait for the second frame for this call,
-                        // until the render thread has definitely kicked in, to avoid a lag.
-                        mCallback.onAnimationToSideStarted(mTranslation < 0);
-                        mCallbackCalled = true;
-                    }
-                    frameNumber++;
-                }
-            });
-        } else {
-            showAllIcons(true);
-        }
-        animator.start();
-        mSwipeAnimator = animator;
-    }
-
-    private void startTranslationAnimations(float vel, float target) {
-        ArrayList<View> targetViews = mCallback.getTranslationViews();
-        for (View targetView : targetViews) {
-            ViewPropertyAnimator animator = targetView.animate();
-            mFlingAnimationUtils.apply(animator, mTranslation, target, vel);
-            animator.translationX(target);
-        }
-    }
-
-    private void startIconAnimation(float vel, boolean snapBack, float target) {
-        float scale = snapBack ? 1.0f : SWIPE_MAX_ICON_SCALE_AMOUNT;
-        float alpha = snapBack ? SWIPE_RESTING_ALPHA_AMOUNT : 1.0f;
-        View targetView = mTranslation > 0
-                ? mLeftIcon
-                : mRightIcon;
-        if (targetView.getVisibility() == View.VISIBLE) {
-            ViewPropertyAnimator iconAnimator = targetView.animate();
-            mFlingAnimationUtils.apply(iconAnimator, mTranslation, target, vel);
-            iconAnimator.scaleX(scale);
-            iconAnimator.scaleY(scale);
-            iconAnimator.alpha(alpha);
-        }
-    }
-
-    private void setTranslation(float translation, boolean isReset) {
-        translation = rightSwipePossible() ? translation : Math.max(0, translation);
-        translation = leftSwipePossible() ? translation : Math.min(0, translation);
-        if (translation != mTranslation || isReset) {
-            ArrayList<View> translatedViews = mCallback.getTranslationViews();
-            for (View view : translatedViews) {
-                view.setTranslationX(translation);
-            }
-            if (translation == 0.0f) {
-                boolean animate = !isReset;
-                showAllIcons(animate);
-            } else {
-                View targetView = translation > 0 ? mLeftIcon : mRightIcon;
-                float progress = Math.abs(translation) / mCallback.getPageWidth();
-                progress = Math.min(progress, 1.0f);
-                float alpha = SWIPE_RESTING_ALPHA_AMOUNT * (1.0f - progress) + progress;
-                float scale = (1.0f - progress) + progress * SWIPE_MAX_ICON_SCALE_AMOUNT;
-                updateIcon(targetView, scale, alpha, false);
-                View otherView = translation < 0 ? mLeftIcon : mRightIcon;
-                if (mTranslation * translation <= 0) {
-                    // The sign of the translation has changed so we need to hide the other icons
-                    updateIcon(otherView, 0, 0, true);
-                    updateIcon(mCenterIcon, 0, 0, true);
-                }
-            }
-            mTranslation = translation;
-        }
-    }
-
-    public void showAllIcons(boolean animate) {
-        float scale = 1.0f;
-        float alpha = SWIPE_RESTING_ALPHA_AMOUNT;
-        updateIcon(mRightIcon, scale, alpha, animate);
-        updateIcon(mCenterIcon, scale, alpha, animate);
-        updateIcon(mLeftIcon, scale, alpha, animate);
-    }
-
-    public void animateHideLeftRightIcon() {
-        updateIcon(mRightIcon, 0f, 0f, true);
-        updateIcon(mLeftIcon, 0f, 0f, true);
-    }
-
-    private void hideInactiveIcons(boolean animate){
-        View otherView = mTranslation < 0 ? mLeftIcon : mRightIcon;
-        updateIcon(otherView, 0, 0, animate);
-        updateIcon(mCenterIcon, 0, 0, animate);
-    }
-
-    private void updateIcon(View view, float scale, float alpha, boolean animate) {
-        if (view.getVisibility() != View.VISIBLE) {
-            return;
-        }
-        if (!animate) {
-            view.animate().cancel();
-            view.setAlpha(alpha);
-            view.setScaleX(scale);
-            view.setScaleY(scale);
-            // TODO: remove this invalidate once the property setters invalidate it properly
-            view.invalidate();
-        } else {
-            if (view.getAlpha() != alpha || view.getScaleX() != scale) {
-                view.animate()
-                        .setInterpolator(mFastOutSlowIn)
-                        .alpha(alpha)
-                        .scaleX(scale)
-                        .scaleY(scale);
-            }
-        }
-    }
-
-    private void trackMovement(MotionEvent event) {
-        if (mVelocityTracker != null) {
-            mVelocityTracker.addMovement(event);
-        }
-    }
-
-    private void initVelocityTracker() {
-        if (mVelocityTracker != null) {
-            mVelocityTracker.recycle();
-        }
-        mVelocityTracker = VelocityTracker.obtain();
-    }
-
-    private float getCurrentVelocity() {
-        if (mVelocityTracker == null) {
-            return 0;
-        }
-        mVelocityTracker.computeCurrentVelocity(1000);
-        return mVelocityTracker.getXVelocity();
-    }
-
-    public void onConfigurationChanged() {
-        initDimens();
-    }
-
-    public void reset() {
-        if (mSwipeAnimator != null) {
-            mSwipeAnimator.cancel();
-        }
-        ArrayList<View> targetViews = mCallback.getTranslationViews();
-        for (View view : targetViews) {
-            view.animate().cancel();
-        }
-        setTranslation(0.0f, true);
-        mSwipingInProgress = false;
-    }
-
-    public boolean isSwipingInProgress() {
-        return mSwipingInProgress;
-    }
-
-    public interface Callback {
-
-        /**
-         * Notifies the callback when an animation to a side page was started.
-         *
-         * @param rightPage Is the page animated to the right page?
-         */
-        void onAnimationToSideStarted(boolean rightPage);
-
-        float getPageWidth();
-
-        ArrayList<View> getTranslationViews();
-
-        View getLeftIcon();
-
-        View getCenterIcon();
-
-        View getRightIcon();
-    }
-}
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 fc0f2d5..11a38b5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -39,6 +39,7 @@
 import com.android.systemui.statusbar.ExpandableView;
 import com.android.systemui.statusbar.FlingAnimationUtils;
 import com.android.systemui.statusbar.GestureRecorder;
+import com.android.systemui.statusbar.KeyguardAffordanceView;
 import com.android.systemui.statusbar.MirrorView;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
@@ -49,7 +50,7 @@
 public class NotificationPanelView extends PanelView implements
         ExpandableView.OnHeightChangedListener, ObservableScrollView.Listener,
         View.OnClickListener, NotificationStackScrollLayout.OnOverscrollTopChangedListener,
-        KeyguardPageSwipeHelper.Callback {
+        KeyguardAffordanceHelper.Callback {
 
     // Cap and total height of Roboto font. Needs to be adjusted when font for the big clock is
     // changed.
@@ -59,7 +60,7 @@
     private static final float HEADER_RUBBERBAND_FACTOR = 2.15f;
     private static final float LOCK_ICON_ACTIVE_SCALE = 1.2f;
 
-    private KeyguardPageSwipeHelper mPageSwiper;
+    private KeyguardAffordanceHelper mAfforanceHelper;
     private StatusBarHeaderView mHeader;
     private View mQsContainer;
     private QSPanel mQsPanel;
@@ -124,7 +125,6 @@
     private boolean mIsExpanding;
 
     private boolean mBlockTouches;
-    private ArrayList<View> mSwipeTranslationViews = new ArrayList<>();
     private int mNotificationScrimWaitDistance;
     private boolean mTwoFingerQsExpand;
     private boolean mTwoFingerQsExpandPossible;
@@ -135,6 +135,10 @@
      */
     private int mScrollYOverride = -1;
     private boolean mQsAnimatorExpand;
+    private boolean mIsLaunchTransitionFinished;
+    private boolean mIsLaunchTransitionRunning;
+    private Runnable mLaunchAnimationEndRunnable;
+    private boolean mOnlyAffordanceInThisMotion;
 
     public NotificationPanelView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -167,9 +171,7 @@
         mFastOutLinearInterpolator = AnimationUtils.loadInterpolator(getContext(),
                 android.R.interpolator.fast_out_linear_in);
         mKeyguardBottomArea = (KeyguardBottomAreaView) findViewById(R.id.keyguard_bottom_area);
-        mSwipeTranslationViews.add(mNotificationStackScroller);
-        mSwipeTranslationViews.add(mKeyguardStatusView);
-        mPageSwiper = new KeyguardPageSwipeHelper(this, getContext());
+        mAfforanceHelper = new KeyguardAffordanceHelper(this, getContext());
     }
 
     @Override
@@ -297,9 +299,10 @@
 
     @Override
     public void resetViews() {
+        mIsLaunchTransitionFinished = false;
         mBlockTouches = false;
         mUnlockIconActive = false;
-        mPageSwiper.reset();
+        mAfforanceHelper.reset(true);
         closeQs();
         mNotificationStackScroller.setOverScrollAmount(0f, true /* onTop */, false /* animate */,
                 true /* cancelAnimators */);
@@ -354,6 +357,7 @@
         if (mBlockTouches) {
             return false;
         }
+        resetDownStates(event);
         int pointerIndex = event.findPointerIndex(mTrackingPointer);
         if (pointerIndex < 0) {
             pointerIndex = 0;
@@ -430,6 +434,12 @@
         return !mQsExpanded && super.onInterceptTouchEvent(event);
     }
 
+    private void resetDownStates(MotionEvent event) {
+        if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+            mOnlyAffordanceInThisMotion = false;
+        }
+    }
+
     @Override
     public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
 
@@ -464,15 +474,14 @@
         if (mBlockTouches) {
             return false;
         }
-        // TODO: Handle doublefinger swipe to notifications again. Look at history for a reference
-        // implementation.
+        resetDownStates(event);
         if ((!mIsExpanding || mHintAnimationRunning)
                 && !mQsExpanded
                 && mStatusBar.getBarState() != StatusBarState.SHADE) {
-            mPageSwiper.onTouchEvent(event);
-            if (mPageSwiper.isSwipingInProgress()) {
-                return true;
-            }
+            mAfforanceHelper.onTouchEvent(event);
+        }
+        if (mOnlyAffordanceInThisMotion) {
+            return true;
         }
         if (event.getActionMasked() == MotionEvent.ACTION_DOWN && getExpandedFraction() == 1f
                 && mStatusBar.getBarState() != StatusBarState.KEYGUARD) {
@@ -951,20 +960,16 @@
         if (mStatusBar.getBarState() == StatusBarState.KEYGUARD
                 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) {
             boolean active = getMaxPanelHeight() - getExpandedHeight() > mUnlockMoveDistance;
+            KeyguardAffordanceView lockIcon = mKeyguardBottomArea.getLockIcon();
             if (active && !mUnlockIconActive && mTracking) {
-                mKeyguardBottomArea.getLockIcon().animate()
-                        .alpha(1f)
-                        .scaleY(LOCK_ICON_ACTIVE_SCALE)
-                        .scaleX(LOCK_ICON_ACTIVE_SCALE)
-                        .setInterpolator(mFastOutLinearInterpolator)
-                        .setDuration(150);
+                lockIcon.setImageAlpha(1.0f, true, 150, mFastOutLinearInterpolator, null);
+                lockIcon.setImageScale(LOCK_ICON_ACTIVE_SCALE, true, 150,
+                        mFastOutLinearInterpolator);
             } else if (!active && mUnlockIconActive && mTracking) {
-                mKeyguardBottomArea.getLockIcon().animate()
-                        .alpha(KeyguardPageSwipeHelper.SWIPE_RESTING_ALPHA_AMOUNT)
-                        .scaleY(1f)
-                        .scaleX(1f)
-                        .setInterpolator(mFastOutLinearInterpolator)
-                        .setDuration(150);
+                lockIcon.setImageAlpha(KeyguardAffordanceHelper.SWIPE_RESTING_ALPHA_AMOUNT, true,
+                        150, mFastOutLinearInterpolator, null);
+                lockIcon.setImageScale(1.0f, true, 150,
+                        mFastOutLinearInterpolator);
             }
             mUnlockIconActive = active;
         }
@@ -1093,7 +1098,7 @@
         super.onTrackingStarted();
         if (mStatusBar.getBarState() == StatusBarState.KEYGUARD
                 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) {
-            mPageSwiper.animateHideLeftRightIcon();
+            mAfforanceHelper.animateHideLeftRightIcon();
         }
     }
 
@@ -1106,16 +1111,15 @@
         }
         if (expand && (mStatusBar.getBarState() == StatusBarState.KEYGUARD
                 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED)) {
-            mPageSwiper.showAllIcons(true);
+            if (!mHintAnimationRunning) {
+                mAfforanceHelper.reset(true);
+            }
         }
         if (!expand && (mStatusBar.getBarState() == StatusBarState.KEYGUARD
                 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED)) {
-            mKeyguardBottomArea.getLockIcon().animate()
-                    .alpha(0f)
-                    .scaleX(2f)
-                    .scaleY(2f)
-                    .setInterpolator(mFastOutLinearInterpolator)
-                    .setDuration(100);
+            KeyguardAffordanceView lockIcon = mKeyguardBottomArea.getLockIcon();
+            lockIcon.setImageAlpha(0.0f, true, 100, mFastOutLinearInterpolator, null);
+            lockIcon.setImageScale(2.0f, true, 100, mFastOutLinearInterpolator);
         }
     }
 
@@ -1141,7 +1145,7 @@
     @Override
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
-        mPageSwiper.onConfigurationChanged();
+        mAfforanceHelper.onConfigurationChanged();
     }
 
     @Override
@@ -1159,6 +1163,8 @@
     @Override
     public void onAnimationToSideStarted(boolean rightPage) {
         boolean start = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? rightPage : !rightPage;
+        mIsLaunchTransitionRunning = true;
+        mLaunchAnimationEndRunnable = null;
         if (start) {
             mKeyguardBottomArea.launchPhone();
         } else {
@@ -1168,20 +1174,29 @@
     }
 
     @Override
+    public void onAnimationToSideEnded() {
+        mIsLaunchTransitionRunning = false;
+        mIsLaunchTransitionFinished = true;
+        if (mLaunchAnimationEndRunnable != null) {
+            mLaunchAnimationEndRunnable.run();
+            mLaunchAnimationEndRunnable = null;
+        }
+    }
+
+    @Override
     protected void onEdgeClicked(boolean right) {
         if ((right && getRightIcon().getVisibility() != View.VISIBLE)
                 || (!right && getLeftIcon().getVisibility() != View.VISIBLE)) {
             return;
         }
         mHintAnimationRunning = true;
-        mPageSwiper.startHintAnimation(right, new Runnable() {
+        mAfforanceHelper.startHintAnimation(right, new Runnable() {
             @Override
             public void run() {
                 mHintAnimationRunning = false;
                 mStatusBar.onHintFinished();
             }
         });
-        startHighlightIconAnimation(right ? getRightIcon() : getLeftIcon());
         boolean start = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? right : !right;
         if (start) {
             mStatusBar.onPhoneHintStarted();
@@ -1199,17 +1214,14 @@
     /**
      * Starts the highlight (making it fully opaque) animation on an icon.
      */
-    private void startHighlightIconAnimation(final View icon) {
-        icon.animate()
-                .alpha(1.0f)
-                .setDuration(KeyguardPageSwipeHelper.HINT_PHASE1_DURATION)
-                .setInterpolator(mFastOutSlowInInterpolator)
-                .withEndAction(new Runnable() {
+    private void startHighlightIconAnimation(final KeyguardAffordanceView icon) {
+        icon.setImageAlpha(1.0f, true, KeyguardAffordanceHelper.HINT_PHASE1_DURATION,
+                mFastOutSlowInInterpolator, new Runnable() {
                     @Override
                     public void run() {
-                        icon.animate().alpha(KeyguardPageSwipeHelper.SWIPE_RESTING_ALPHA_AMOUNT)
-                                .setDuration(KeyguardPageSwipeHelper.HINT_PHASE1_DURATION)
-                                .setInterpolator(mFastOutSlowInInterpolator);
+                        icon.setImageAlpha(KeyguardAffordanceHelper.SWIPE_RESTING_ALPHA_AMOUNT,
+                                true, KeyguardAffordanceHelper.HINT_PHASE1_DURATION,
+                                mFastOutSlowInInterpolator, null);
                     }
                 });
     }
@@ -1220,27 +1232,28 @@
     }
 
     @Override
-    public ArrayList<View> getTranslationViews() {
-        return mSwipeTranslationViews;
+    public void onSwipingStarted() {
+        requestDisallowInterceptTouchEvent(true);
+        mOnlyAffordanceInThisMotion = true;
     }
 
     @Override
-    public View getLeftIcon() {
+    public KeyguardAffordanceView getLeftIcon() {
         return getLayoutDirection() == LAYOUT_DIRECTION_RTL
-                ? mKeyguardBottomArea.getCameraImageView()
-                : mKeyguardBottomArea.getPhoneImageView();
+                ? mKeyguardBottomArea.getCameraView()
+                : mKeyguardBottomArea.getPhoneView();
     }
 
     @Override
-    public View getCenterIcon() {
+    public KeyguardAffordanceView getCenterIcon() {
         return mKeyguardBottomArea.getLockIcon();
     }
 
     @Override
-    public View getRightIcon() {
+    public KeyguardAffordanceView getRightIcon() {
         return getLayoutDirection() == LAYOUT_DIRECTION_RTL
-                ? mKeyguardBottomArea.getPhoneImageView()
-                : mKeyguardBottomArea.getCameraImageView();
+                ? mKeyguardBottomArea.getPhoneView()
+                : mKeyguardBottomArea.getCameraView();
     }
 
     @Override
@@ -1282,4 +1295,16 @@
     public boolean shouldDelayChildPressedState() {
         return true;
     }
+
+    public boolean isLaunchTransitionFinished() {
+        return mIsLaunchTransitionFinished;
+    }
+
+    public boolean isLaunchTransitionRunning() {
+        return mIsLaunchTransitionRunning;
+    }
+
+    public void setLaunchTransitionEndRunnable(Runnable r) {
+        mLaunchAnimationEndRunnable = r;
+    }
 }
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 e4e67c9..46a32da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -53,7 +53,6 @@
 import android.graphics.Point;
 import android.graphics.PorterDuff;
 import android.graphics.Rect;
-import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.inputmethodservice.InputMethodService;
@@ -63,6 +62,7 @@
 import android.media.session.MediaSession;
 import android.media.session.MediaSessionManager;
 import android.media.session.PlaybackState;
+import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
@@ -70,6 +70,7 @@
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.SystemClock;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.service.notification.NotificationListenerService.RankingMap;
@@ -201,6 +202,9 @@
             .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
             .build();
 
+    public static final int FADE_KEYGUARD_START_DELAY = 100;
+    public static final int FADE_KEYGUARD_DURATION = 300;
+
     PhoneStatusBarPolicy mIconPolicy;
 
     // These are no longer handled by the policy, because we need custom strategies for them
@@ -441,6 +445,8 @@
     private final ShadeUpdates mShadeUpdates = new ShadeUpdates();
 
     private int mDrawCount;
+    private Runnable mLaunchTransitionEndRunnable;
+    private boolean mLaunchTransitionFadingAway;
 
     private static final int VISIBLE_LOCATIONS = ViewState.LOCATION_FIRST_CARD
             | ViewState.LOCATION_TOP_STACK_PEEKING
@@ -1728,7 +1734,8 @@
     }
 
     private int adjustDisableFlags(int state) {
-        if (mExpandedVisible || mBouncerShowing || mWaitingForKeyguardExit) {
+        if (!mLaunchTransitionFadingAway
+                && (mExpandedVisible || mBouncerShowing || mWaitingForKeyguardExit)) {
             state |= StatusBarManager.DISABLE_NOTIFICATION_ICONS;
             state |= StatusBarManager.DISABLE_SYSTEM_INFO;
         }
@@ -2725,13 +2732,18 @@
         dismissKeyguardThenExecute(new OnDismissAction() {
             @Override
             public boolean onDismiss() {
-                try {
-                    // Dismiss the lock screen when Settings starts.
-                    ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity();
-                } catch (RemoteException e) {
-                }
-                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
-                mContext.startActivityAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
+                AsyncTask.execute(new Runnable() {
+                    public void run() {
+                        try {
+                            intent.setFlags(
+                                    Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+                            mContext.startActivityAsUser(
+                                    intent, new UserHandle(UserHandle.USER_CURRENT));
+                            mWindowManagerService.overridePendingAppTransition(null, 0, 0, null);
+                        } catch (RemoteException e) {
+                        }
+                    }
+                });
                 animateCollapsePanels();
 
                 return DELAY_DISMISS_TO_ACTIVITY_LAUNCH;
@@ -2795,9 +2807,20 @@
     };
 
     @Override
-    protected void dismissKeyguardThenExecute(OnDismissAction action) {
+    protected void dismissKeyguardThenExecute(final OnDismissAction action) {
         if (mStatusBarKeyguardViewManager.isShowing()) {
-            mStatusBarKeyguardViewManager.dismissWithAction(action);
+            if (UnlockMethodCache.getInstance(mContext).isMethodInsecure()
+                    && mNotificationPanel.isLaunchTransitionRunning()) {
+                action.onDismiss();
+                mNotificationPanel.setLaunchTransitionEndRunnable(new Runnable() {
+                    @Override
+                    public void run() {
+                        mStatusBarKeyguardViewManager.dismiss();
+                    }
+                });
+            } else {
+                mStatusBarKeyguardViewManager.dismissWithAction(action);
+            }
         } else {
             action.onDismiss();
         }
@@ -3170,6 +3193,52 @@
         mLeaveOpenOnKeyguardHide = false;
     }
 
+    public boolean isInLaunchTransition() {
+        return mNotificationPanel.isLaunchTransitionRunning()
+                || mNotificationPanel.isLaunchTransitionFinished();
+    }
+
+    /**
+     * Fades the content of the keyguard away after the launch transition is done.
+     *
+     * @param beforeFading the runnable to be run when the circle is fully expanded and the fading
+     *                     starts
+     * @param endRunnable the runnable to be run when the transition is done
+     */
+    public void fadeKeyguardAfterLaunchTransition(final Runnable beforeFading,
+            final Runnable endRunnable) {
+        Runnable hideRunnable = new Runnable() {
+            @Override
+            public void run() {
+                mLaunchTransitionFadingAway = true;
+                if (beforeFading != null) {
+                    beforeFading.run();
+                }
+                mNotificationPanel.setAlpha(1);
+                mNotificationPanel.animate()
+                        .alpha(0)
+                        .setStartDelay(FADE_KEYGUARD_START_DELAY)
+                        .setDuration(FADE_KEYGUARD_DURATION)
+                        .withLayer()
+                        .withEndAction(new Runnable() {
+                            @Override
+                            public void run() {
+                                mNotificationPanel.setAlpha(1);
+                                if (endRunnable != null) {
+                                    endRunnable.run();
+                                }
+                                mLaunchTransitionFadingAway = false;
+                            }
+                        });
+            }
+        };
+        if (mNotificationPanel.isLaunchTransitionRunning()) {
+            mNotificationPanel.setLaunchTransitionEndRunnable(hideRunnable);
+        } else {
+            hideRunnable.run();
+        }
+    }
+
     public void hideKeyguard() {
         setBarState(StatusBarState.SHADE);
         if (mLeaveOpenOnKeyguardHide) {
@@ -3204,8 +3273,8 @@
 
     private void updatePublicMode() {
         setLockscreenPublicMode(
-                (mStatusBarKeyguardViewManager.isShowing() || 
-                    mStatusBarKeyguardViewManager.isOccluded())
+                (mStatusBarKeyguardViewManager.isShowing() ||
+                        mStatusBarKeyguardViewManager.isOccluded())
                 && mStatusBarKeyguardViewManager.isSecure());
     }
 
@@ -3315,7 +3384,7 @@
 
     private void showBouncer() {
         if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) {
-            mWaitingForKeyguardExit = true;
+            mWaitingForKeyguardExit = mStatusBarKeyguardViewManager.isShowing();
             mStatusBarKeyguardViewManager.dismiss();
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index cf930bd..eb42401 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -71,7 +71,6 @@
     private boolean mAnimationStarted;
     private boolean mDozing;
     private int mTeasesRemaining;
-
     private final Interpolator mInterpolator = new DecelerateInterpolator();
 
     public ScrimController(View scrimBehind, View scrimInFront) {
@@ -149,7 +148,10 @@
     }
 
     private void updateScrims() {
-        if ((!mKeyguardShowing && !mBouncerShowing) || mAnimateKeyguardFadingOut) {
+        if (mAnimateKeyguardFadingOut) {
+            setScrimInFrontColor(0f);
+            setScrimBehindColor(0f);
+        }else if (!mKeyguardShowing && !mBouncerShowing) {
             updateScrimNormal();
             setScrimInFrontColor(0);
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 93dcf90..af21f25 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -176,6 +176,19 @@
     }
 
     public void setOccluded(boolean occluded) {
+        if (occluded && !mOccluded && mShowing) {
+            if (mPhoneStatusBar.isInLaunchTransition()) {
+                mOccluded = true;
+                mPhoneStatusBar.fadeKeyguardAfterLaunchTransition(null /* beforeFading */,
+                        new Runnable() {
+                            @Override
+                            public void run() {
+                                mStatusBarWindowManager.setKeyguardOccluded(true);
+                            }
+                        });
+                return;
+            }
+        }
         mOccluded = occluded;
         mStatusBarWindowManager.setKeyguardOccluded(occluded);
         reset();
@@ -188,29 +201,50 @@
     /**
      * Hides the keyguard view
      */
-    public void hide(long startTime, long fadeoutDuration) {
+    public void hide(long startTime, final long fadeoutDuration) {
         mShowing = false;
 
         long uptimeMillis = SystemClock.uptimeMillis();
-        long delay = startTime - uptimeMillis;
-        if (delay < 0) {
-            delay = 0;
+        long delay = Math.max(0, startTime - uptimeMillis);
+
+        if (mPhoneStatusBar.isInLaunchTransition() ) {
+            mPhoneStatusBar.fadeKeyguardAfterLaunchTransition(new Runnable() {
+                @Override
+                public void run() {
+                    mStatusBarWindowManager.setKeyguardShowing(false);
+                    mStatusBarWindowManager.setKeyguardFadingAway(true);
+                    mBouncer.animateHide(PhoneStatusBar.FADE_KEYGUARD_START_DELAY,
+                            PhoneStatusBar.FADE_KEYGUARD_DURATION);
+                    updateStates();
+                    mScrimController.animateKeyguardFadingOut(
+                            PhoneStatusBar.FADE_KEYGUARD_START_DELAY,
+                            PhoneStatusBar.FADE_KEYGUARD_DURATION, null);
+                }
+            }, new Runnable() {
+                @Override
+                public void run() {
+                    mPhoneStatusBar.hideKeyguard();
+                    mStatusBarWindowManager.setKeyguardFadingAway(false);
+                    mViewMediatorCallback.keyguardGone();
+                }
+            });
+        } else {
+            mPhoneStatusBar.setKeyguardFadingAway(delay, fadeoutDuration);
+            mPhoneStatusBar.hideKeyguard();
+            mStatusBarWindowManager.setKeyguardFadingAway(true);
+            mStatusBarWindowManager.setKeyguardShowing(false);
+            mBouncer.animateHide(delay, fadeoutDuration);
+            mScrimController.animateKeyguardFadingOut(delay, fadeoutDuration, new Runnable() {
+                @Override
+                public void run() {
+                    mStatusBarWindowManager.setKeyguardFadingAway(false);
+                    mPhoneStatusBar.finishKeyguardFadingAway();
+                }
+            });
+            mViewMediatorCallback.keyguardGone();
+            updateStates();
         }
 
-        mPhoneStatusBar.setKeyguardFadingAway(delay, fadeoutDuration);
-        mPhoneStatusBar.hideKeyguard();
-        mStatusBarWindowManager.setKeyguardFadingAway(true);
-        mStatusBarWindowManager.setKeyguardShowing(false);
-        mBouncer.animateHide(delay, fadeoutDuration);
-        mScrimController.animateKeyguardFadingOut(delay, fadeoutDuration, new Runnable() {
-            @Override
-            public void run() {
-                mStatusBarWindowManager.setKeyguardFadingAway(false);
-                mPhoneStatusBar.finishKeyguardFadingAway();
-            }
-        });
-        mViewMediatorCallback.keyguardGone();
-        updateStates();
     }
 
     /**