Merge "Add Calls.TRANSCRIPTION to CallLog (2/2)" into lmp-dev
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index a193a34..1f2f18a 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -18,6 +18,7 @@
 
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemApi;
 import android.app.Activity;
 import android.content.AbstractRestrictionsProvider;
 import android.content.ComponentName;
@@ -2083,7 +2084,6 @@
 
     /**
      * @hide
-     * @SystemApi
      * Sets the given component as an active admin and registers the package as the profile
      * owner for this user. The package must already be installed and there shouldn't be
      * an existing profile owner registered for this user. Also, this method must be called
@@ -2097,6 +2097,7 @@
      * @throws IllegalArgumentException if packageName is null, the package isn't installed, or
      *         the user has already been set up.
      */
+    @SystemApi
     public boolean setActiveProfileOwner(ComponentName admin, String ownerName)
             throws IllegalArgumentException {
         if (mService != null) {
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index b033780..d14f226 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -88,8 +88,8 @@
     private final float mLightY;
     private final float mLightZ;
     private final float mLightRadius;
-    private final float mAmbientShadowAlpha;
-    private final float mSpotShadowAlpha;
+    private final int mAmbientShadowAlpha;
+    private final int mSpotShadowAlpha;
 
     private long mNativeProxy;
     private boolean mInitialized = false;
@@ -104,8 +104,10 @@
         mLightY = a.getDimension(R.styleable.Lighting_lightY, 0);
         mLightZ = a.getDimension(R.styleable.Lighting_lightZ, 0);
         mLightRadius = a.getDimension(R.styleable.Lighting_lightRadius, 0);
-        mAmbientShadowAlpha = a.getFloat(R.styleable.Lighting_ambientShadowAlpha, 0);
-        mSpotShadowAlpha = a.getFloat(R.styleable.Lighting_spotShadowAlpha, 0);
+        mAmbientShadowAlpha = Math.round(
+                255 * a.getFloat(R.styleable.Lighting_ambientShadowAlpha, 0));
+        mSpotShadowAlpha = Math.round(
+                255 * a.getFloat(R.styleable.Lighting_spotShadowAlpha, 0));
         a.recycle();
 
         long rootNodePtr = nCreateRootRenderNode();
@@ -208,7 +210,9 @@
             mSurfaceHeight = height;
         }
         mRootNode.setLeftTopRightBottom(-mInsetLeft, -mInsetTop, mSurfaceWidth, mSurfaceHeight);
-        nSetup(mNativeProxy, mSurfaceWidth, mSurfaceHeight, lightX, mLightY, mLightZ, mLightRadius);
+        nSetup(mNativeProxy, mSurfaceWidth, mSurfaceHeight,
+                lightX, mLightY, mLightZ, mLightRadius,
+                mAmbientShadowAlpha, mSpotShadowAlpha);
     }
 
     @Override
@@ -453,7 +457,8 @@
     private static native void nUpdateSurface(long nativeProxy, Surface window);
     private static native void nPauseSurface(long nativeProxy, Surface window);
     private static native void nSetup(long nativeProxy, int width, int height,
-            float lightX, float lightY, float lightZ, float lightRadius);
+            float lightX, float lightY, float lightZ, float lightRadius,
+            int ambientShadowAlpha, int spotShadowAlpha);
     private static native void nSetOpaque(long nativeProxy, boolean opaque);
     private static native int nSyncAndDrawFrame(long nativeProxy,
             long frameTimeNanos, long recordDuration, float density);
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index cc41669..92703ab 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -88,7 +88,7 @@
  * </pre>
  * <p>See {@link android.content.Intent} for more information.</p>
  *
- * <p>To provide a WebView in your own Activity, include a {@code <WebView>} in your layout,
+ * <p>To provide a WebView in your own Activity, include a {@code &lt;WebView&gt;} in your layout,
  * or set the entire Activity window as a WebView during {@link
  * android.app.Activity#onCreate(Bundle) onCreate()}:</p>
  * <pre class="prettyprint">
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index 988d461..ec08a4f 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -219,9 +219,11 @@
 
 static void android_view_ThreadedRenderer_setup(JNIEnv* env, jobject clazz, jlong proxyPtr,
         jint width, jint height,
-        jfloat lightX, jfloat lightY, jfloat lightZ, jfloat lightRadius) {
+        jfloat lightX, jfloat lightY, jfloat lightZ, jfloat lightRadius,
+        jint ambientShadowAlpha, jint spotShadowAlpha) {
     RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
-    proxy->setup(width, height, Vector3(lightX, lightY, lightZ), lightRadius);
+    proxy->setup(width, height, Vector3(lightX, lightY, lightZ), lightRadius,
+            ambientShadowAlpha, spotShadowAlpha);
 }
 
 static void android_view_ThreadedRenderer_setOpaque(JNIEnv* env, jobject clazz,
@@ -358,7 +360,7 @@
     { "nInitialize", "(JLandroid/view/Surface;)Z", (void*) android_view_ThreadedRenderer_initialize },
     { "nUpdateSurface", "(JLandroid/view/Surface;)V", (void*) android_view_ThreadedRenderer_updateSurface },
     { "nPauseSurface", "(JLandroid/view/Surface;)V", (void*) android_view_ThreadedRenderer_pauseSurface },
-    { "nSetup", "(JIIFFFF)V", (void*) android_view_ThreadedRenderer_setup },
+    { "nSetup", "(JIIFFFFII)V", (void*) android_view_ThreadedRenderer_setup },
     { "nSetOpaque", "(JZ)V", (void*) android_view_ThreadedRenderer_setOpaque },
     { "nSyncAndDrawFrame", "(JJJF)I", (void*) android_view_ThreadedRenderer_syncAndDrawFrame },
     { "nDestroyCanvasAndSurface", "(J)V", (void*) android_view_ThreadedRenderer_destroyCanvasAndSurface },
diff --git a/docs/html/guide/topics/resources/localization.jd b/docs/html/guide/topics/resources/localization.jd
index e86d4c9..1ee6606 100644
--- a/docs/html/guide/topics/resources/localization.jd
+++ b/docs/html/guide/topics/resources/localization.jd
@@ -402,8 +402,7 @@
 	resolution and density of the device screen may differ, which could affect 

 	the display of strings and drawables in your UI.</p>

 

-<p>To change the locale on a device, use  the Settings application  (Home &gt;

-Menu &gt; Settings &gt; Locale &amp; text &gt; Select locale). </p>

+<p>To change the locale or language on a device, use the Settings application.</p>

 

 <h3 id="emulator">Testing on an Emulator</h3>

 

diff --git a/docs/html/preview/material/ui-widgets.jd b/docs/html/preview/material/ui-widgets.jd
index 2d29420..69b7d2d 100644
--- a/docs/html/preview/material/ui-widgets.jd
+++ b/docs/html/preview/material/ui-widgets.jd
@@ -132,7 +132,7 @@
                                                    int viewType) {
         // create a new view
         View v = LayoutInflater.from(parent.getContext())
-                               .inflate(R.layout.my_text_view, parent);
+                               .inflate(R.layout.my_text_view, parent, false);
         // set the view's size, margins, paddings and layout parameters
         ...
         ViewHolder vh = new ViewHolder(v);
diff --git a/graphics/java/android/graphics/BlurMaskFilter.java b/graphics/java/android/graphics/BlurMaskFilter.java
index 939af52..f3064f8 100644
--- a/graphics/java/android/graphics/BlurMaskFilter.java
+++ b/graphics/java/android/graphics/BlurMaskFilter.java
@@ -25,10 +25,25 @@
 public class BlurMaskFilter extends MaskFilter {
 
     public enum Blur {
-        NORMAL(0),  //!< blur inside and outside of the original border
-        SOLID(1),   //!< include the original mask, blur outside
-        OUTER(2),   //!< just blur outside the original border
-        INNER(3);   //!< just blur inside the original border
+        /**
+         * Blur inside and outside the original border.
+         */
+        NORMAL(0),
+
+        /**
+         * Draw solid inside the border, blur outside.
+         */
+        SOLID(1),
+
+        /**
+         * Draw nothing inside the border, blur outside.
+         */
+        OUTER(2),
+
+        /**
+         * Blur inside the border, draw nothing outside.
+         */
+        INNER(3);
         
         Blur(int value) {
             native_int = value;
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 5a96132..4f81066 100755
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -132,19 +132,21 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 OpenGLRenderer::OpenGLRenderer(RenderState& renderState)
-        : mCaches(Caches::getInstance())
+        : mFrameStarted(false)
+        , mCaches(Caches::getInstance())
         , mExtensions(Extensions::getInstance())
-        , mRenderState(renderState) {
+        , mRenderState(renderState)
+        , mScissorOptimizationDisabled(false)
+        , mCountOverdraw(false)
+        , mLightCenter(FLT_MIN, FLT_MIN, FLT_MIN)
+        , mLightRadius(FLT_MIN)
+        , mAmbientShadowAlpha(0)
+        , mSpotShadowAlpha(0) {
     // *set* draw modifiers to be 0
     memset(&mDrawModifiers, 0, sizeof(mDrawModifiers));
     mDrawModifiers.mOverrideLayerAlpha = 1.0f;
 
     memcpy(mMeshVertices, gMeshVertices, sizeof(gMeshVertices));
-
-    mFrameStarted = false;
-    mCountOverdraw = false;
-
-    mScissorOptimizationDisabled = false;
 }
 
 OpenGLRenderer::~OpenGLRenderer() {
@@ -163,6 +165,14 @@
     }
 }
 
+void OpenGLRenderer::initLight(const Vector3& lightCenter, float lightRadius,
+        uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha) {
+    mLightCenter = lightCenter;
+    mLightRadius = lightRadius;
+    mAmbientShadowAlpha = ambientShadowAlpha;
+    mSpotShadowAlpha = spotShadowAlpha;
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 // Setup
 ///////////////////////////////////////////////////////////////////////////////
@@ -3172,13 +3182,13 @@
     SkPaint paint;
     paint.setAntiAlias(true); // want to use AlphaVertex
 
-    if (ambientShadowVertexBuffer && mCaches.propertyAmbientShadowStrength > 0) {
-        paint.setARGB(casterAlpha * mCaches.propertyAmbientShadowStrength, 0, 0, 0);
+    if (ambientShadowVertexBuffer && mAmbientShadowAlpha > 0) {
+        paint.setARGB(casterAlpha * mAmbientShadowAlpha, 0, 0, 0);
         drawVertexBuffer(*ambientShadowVertexBuffer, &paint);
     }
 
-    if (spotShadowVertexBuffer && mCaches.propertySpotShadowStrength > 0) {
-        paint.setARGB(casterAlpha * mCaches.propertySpotShadowStrength, 0, 0, 0);
+    if (spotShadowVertexBuffer && mSpotShadowAlpha > 0) {
+        paint.setARGB(casterAlpha * mSpotShadowAlpha, 0, 0, 0);
         drawVertexBuffer(*spotShadowVertexBuffer, &paint);
     }
 
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index 4e7844b..f698b45 100755
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -124,6 +124,8 @@
     virtual ~OpenGLRenderer();
 
     void initProperties();
+    void initLight(const Vector3& lightCenter, float lightRadius,
+            uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha);
 
     virtual void onViewportInitialized();
     virtual status_t prepareDirty(float left, float top, float right, float bottom, bool opaque);
@@ -1010,6 +1012,12 @@
 
     bool mSkipOutlineClip;
 
+    // Lighting + shadows
+    Vector3 mLightCenter;
+    float mLightRadius;
+    uint8_t mAmbientShadowAlpha;
+    uint8_t mSpotShadowAlpha;
+
     friend class Layer;
     friend class TextSetupFunctor;
     friend class DrawBitmapOp;
diff --git a/libs/hwui/Renderer.h b/libs/hwui/Renderer.h
index ccd3ba5..40a21e4 100644
--- a/libs/hwui/Renderer.h
+++ b/libs/hwui/Renderer.h
@@ -80,14 +80,6 @@
     virtual void setViewport(int width, int height) = 0;
 
     /**
-     * Sets the position and size of the spot shadow casting light.
-     *
-     * @param lightCenter The light's Y position, relative to the render target's top left
-     * @param lightRadius The light's radius
-     */
-    virtual void initializeLight(const Vector3& lightCenter, float lightRadius) = 0;
-
-    /**
      * Prepares the renderer to draw a frame. This method must be invoked
      * at the beginning of each frame. When this method is invoked, the
      * entire drawing surface is assumed to be redrawn.
diff --git a/libs/hwui/StatefulBaseRenderer.cpp b/libs/hwui/StatefulBaseRenderer.cpp
index 95c0ee5..140c6e8 100644
--- a/libs/hwui/StatefulBaseRenderer.cpp
+++ b/libs/hwui/StatefulBaseRenderer.cpp
@@ -23,10 +23,13 @@
 namespace android {
 namespace uirenderer {
 
-StatefulBaseRenderer::StatefulBaseRenderer() :
-        mDirtyClip(false), mWidth(-1), mHeight(-1),
-        mSaveCount(1), mFirstSnapshot(new Snapshot), mSnapshot(mFirstSnapshot),
-        mLightCenter(FLT_MIN, FLT_MIN, FLT_MIN), mLightRadius(FLT_MIN) {
+StatefulBaseRenderer::StatefulBaseRenderer()
+        : mDirtyClip(false)
+        , mWidth(-1)
+        , mHeight(-1)
+        , mSaveCount(1)
+        , mFirstSnapshot(new Snapshot)
+        , mSnapshot(mFirstSnapshot) {
 }
 
 void StatefulBaseRenderer::initializeSaveStack(float clipLeft, float clipTop,
@@ -45,11 +48,6 @@
     onViewportInitialized();
 }
 
-void StatefulBaseRenderer::initializeLight(const Vector3& lightCenter, float lightRadius) {
-    mLightCenter = lightCenter;
-    mLightRadius = lightRadius;
-}
-
 ///////////////////////////////////////////////////////////////////////////////
 // Save (layer)
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/libs/hwui/StatefulBaseRenderer.h b/libs/hwui/StatefulBaseRenderer.h
index e8e024f..25cc832 100644
--- a/libs/hwui/StatefulBaseRenderer.h
+++ b/libs/hwui/StatefulBaseRenderer.h
@@ -52,7 +52,6 @@
      * the render target.
      */
     virtual void setViewport(int width, int height);
-    virtual void initializeLight(const Vector3& lightCenter, float lightRadius);
     void initializeSaveStack(float clipLeft, float clipTop, float clipRight, float clipBottom);
 
     // getters
@@ -161,10 +160,6 @@
     // Current state
     // TODO: should become private, once hooks needed by OpenGLRenderer are added
     sp<Snapshot> mSnapshot;
-
-    Vector3 mLightCenter;
-    float mLightRadius;
-
 }; // class StatefulBaseRenderer
 
 }; // namespace uirenderer
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 2147810..756f660 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -110,10 +110,11 @@
     // and such to prevent from trying to render into this surface
 }
 
-void CanvasContext::setup(int width, int height, const Vector3& lightCenter, float lightRadius) {
+void CanvasContext::setup(int width, int height, const Vector3& lightCenter, float lightRadius,
+        uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha) {
     if (!mCanvas) return;
     mCanvas->setViewport(width, height);
-    mCanvas->initializeLight(lightCenter, lightRadius);
+    mCanvas->initLight(lightCenter, lightRadius, ambientShadowAlpha, spotShadowAlpha);
 }
 
 void CanvasContext::setOpaque(bool opaque) {
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 1bab1b1..2a01027 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -53,7 +53,8 @@
     bool initialize(ANativeWindow* window);
     void updateSurface(ANativeWindow* window);
     void pauseSurface(ANativeWindow* window);
-    void setup(int width, int height, const Vector3& lightCenter, float lightRadius);
+    void setup(int width, int height, const Vector3& lightCenter, float lightRadius,
+            uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha);
     void setOpaque(bool opaque);
     void makeCurrent();
     void processLayerUpdate(DeferredLayerUpdater* layerUpdater);
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 91f5801..1e91eb5 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -39,6 +39,8 @@
 #define CREATE_BRIDGE3(name, a1, a2, a3) CREATE_BRIDGE(name, a1,a2,a3,,,,,)
 #define CREATE_BRIDGE4(name, a1, a2, a3, a4) CREATE_BRIDGE(name, a1,a2,a3,a4,,,,)
 #define CREATE_BRIDGE5(name, a1, a2, a3, a4, a5) CREATE_BRIDGE(name, a1,a2,a3,a4,a5,,,)
+#define CREATE_BRIDGE6(name, a1, a2, a3, a4, a5, a6) CREATE_BRIDGE(name, a1,a2,a3,a4,a5,a6,,)
+#define CREATE_BRIDGE7(name, a1, a2, a3, a4, a5, a6, a7) CREATE_BRIDGE(name, a1,a2,a3,a4,a5,a6,a7,)
 #define CREATE_BRIDGE(name, a1, a2, a3, a4, a5, a6, a7, a8) \
     typedef struct { \
         a1; a2; a3; a4; a5; a6; a7; a8; \
@@ -152,19 +154,24 @@
     postAndWait(task);
 }
 
-CREATE_BRIDGE5(setup, CanvasContext* context, int width, int height,
-        Vector3 lightCenter, int lightRadius) {
-    args->context->setup(args->width, args->height, args->lightCenter, args->lightRadius);
+CREATE_BRIDGE7(setup, CanvasContext* context, int width, int height,
+        Vector3 lightCenter, float lightRadius,
+        uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha) {
+    args->context->setup(args->width, args->height, args->lightCenter, args->lightRadius,
+            args->ambientShadowAlpha, args->spotShadowAlpha);
     return NULL;
 }
 
-void RenderProxy::setup(int width, int height, const Vector3& lightCenter, float lightRadius) {
+void RenderProxy::setup(int width, int height, const Vector3& lightCenter, float lightRadius,
+        uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha) {
     SETUP_TASK(setup);
     args->context = mContext;
     args->width = width;
     args->height = height;
     args->lightCenter = lightCenter;
     args->lightRadius = lightRadius;
+    args->ambientShadowAlpha = ambientShadowAlpha;
+    args->spotShadowAlpha = spotShadowAlpha;
     post(task);
 }
 
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index 0027403..28d0173 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -67,7 +67,8 @@
     ANDROID_API bool initialize(const sp<ANativeWindow>& window);
     ANDROID_API void updateSurface(const sp<ANativeWindow>& window);
     ANDROID_API void pauseSurface(const sp<ANativeWindow>& window);
-    ANDROID_API void setup(int width, int height, const Vector3& lightCenter, float lightRadius);
+    ANDROID_API void setup(int width, int height, const Vector3& lightCenter, float lightRadius,
+            uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha);
     ANDROID_API void setOpaque(bool opaque);
     ANDROID_API int syncAndDrawFrame(nsecs_t frameTimeNanos, nsecs_t recordDurationNanos,
             float density);
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
index 86c3a92..a1b1aec 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
@@ -121,6 +121,7 @@
     private static final int STATE_CREATE_FILE_FAILED = 4;
     private static final int STATE_PRINTER_UNAVAILABLE = 5;
     private static final int STATE_UPDATE_SLOW = 6;
+    private static final int STATE_PRINT_COMPLETED = 7;
 
     private static final int UI_STATE_PREVIEW = 0;
     private static final int UI_STATE_ERROR = 1;
@@ -304,6 +305,10 @@
                     spooler.setPrintJobState(mPrintJob.getId(), PrintJobInfo.STATE_QUEUED, null);
                 } break;
 
+                case STATE_PRINT_COMPLETED: {
+                    spooler.setPrintJobState(mPrintJob.getId(), PrintJobInfo.STATE_COMPLETED, null);
+                } break;
+
                 case STATE_CREATE_FILE_FAILED: {
                     spooler.setPrintJobState(mPrintJob.getId(), PrintJobInfo.STATE_FAILED,
                             getString(R.string.print_write_error_message));
@@ -539,6 +544,8 @@
 
     private void onStartCreateDocumentActivityResult(int resultCode, Intent data) {
         if (resultCode == RESULT_OK && data != null) {
+            setState(STATE_PRINT_COMPLETED);
+            updateOptionsUi();
             Uri uri = data.getData();
             mPrintedDocument.writeContent(getContentResolver(), uri);
             // Calling finish here does not invoke lifecycle callbacks but we
@@ -706,7 +713,8 @@
 
     private static boolean isFinalState(int state) {
         return state == STATE_PRINT_CONFIRMED
-                || state == STATE_PRINT_CANCELED;
+                || state == STATE_PRINT_CANCELED
+                || state == STATE_PRINT_COMPLETED;
     }
 
     private void updateSelectedPagesFromPreview() {
@@ -1060,6 +1068,7 @@
         }
 
         if (mState == STATE_PRINT_CONFIRMED
+                || mState == STATE_PRINT_COMPLETED
                 || mState == STATE_PRINT_CANCELED
                 || mState == STATE_UPDATE_FAILED
                 || mState == STATE_CREATE_FILE_FAILED
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..04fc02c 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) {
                         }
 
@@ -1012,8 +1009,6 @@
             expanded.setExpandedChild(bigContentViewLocal);
         }
 
-        PackageManager pm = mContext.getPackageManager();
-
         // now the public version
         View publicViewLocal = null;
         if (publicNotification != null) {
@@ -1034,6 +1029,9 @@
         }
 
         if (publicViewLocal == null) {
+            PackageManager pm = getPackageManagerForUser(
+                    entry.notification.getUser().getIdentifier());
+
             // Add a basic notification template
             publicViewLocal = LayoutInflater.from(mContext).inflate(
                     com.android.internal.R.layout.notification_template_material_base,
@@ -1153,9 +1151,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) {
                     }
 
@@ -1670,4 +1665,26 @@
             // Ignore.
         }
     }
+
+    /**
+     * @return a PackageManger for userId or if userId is < 0 (USER_ALL etc) then
+     *         return PackageManager for mContext
+     */
+    protected PackageManager getPackageManagerForUser(int userId) {
+        Context contextForUser = mContext;
+        // UserHandle defines special userId as negative values, e.g. USER_ALL
+        if (userId >= 0) {
+            try {
+                // Create a context for the correct user so if a package isn't installed
+                // for user 0 we can still load information about the package.
+                contextForUser =
+                        mContext.createPackageContextAsUser(mContext.getPackageName(),
+                        Context.CONTEXT_RESTRICTED,
+                        new UserHandle(userId));
+            } catch (NameNotFoundException e) {
+                // Shouldn't fail to find the package name for system ui.
+            }
+        }
+        return contextForUser.getPackageManager();
+    }
 }
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/MultiUserSwitch.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
index 688c0d8..af30266 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
@@ -26,7 +26,7 @@
 import android.widget.FrameLayout;
 
 import com.android.systemui.qs.QSPanel;
-import com.android.systemui.qs.tiles.UserDetailView;
+import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
 
 /**
  * Container for image of the multi user switcher (tappable).
@@ -34,6 +34,8 @@
 public class MultiUserSwitch extends FrameLayout implements View.OnClickListener {
 
     private QSPanel mQsPanel;
+    private KeyguardUserSwitcher mKeyguardUserSwitcher;
+    private boolean mKeyguardMode;
 
     public MultiUserSwitch(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -49,12 +51,26 @@
         mQsPanel = qsPanel;
     }
 
+    public void setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) {
+        mKeyguardUserSwitcher = keyguardUserSwitcher;
+    }
+
+    public void setKeyguardMode(boolean keyguardShowing) {
+        mKeyguardMode = keyguardShowing;
+    }
+
     @Override
     public void onClick(View v) {
         final UserManager um = UserManager.get(getContext());
         if (um.isUserSwitcherEnabled()) {
-            mQsPanel.showDetailAdapter(true,
-                    mQsPanel.getHost().getUserSwitcherController().userDetailAdapter);
+            if (mKeyguardMode) {
+                if (mKeyguardUserSwitcher != null) {
+                    mKeyguardUserSwitcher.show();
+                }
+            } else {
+                mQsPanel.showDetailAdapter(true,
+                        mQsPanel.getHost().getUserSwitcherController().userDetailAdapter);
+            }
         } else {
             Intent intent = ContactsContract.QuickContact.composeQuickContactsIntent(
                     getContext(), v, ContactsContract.Profile.CONTENT_URI,
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..b3b70e8 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
@@ -731,9 +737,6 @@
         final SignalClusterView signalCluster =
                 (SignalClusterView)mStatusBarView.findViewById(R.id.signal_cluster);
 
-        mKeyguardUserSwitcher = new KeyguardUserSwitcher(mContext,
-                (ViewStub) mStatusBarWindow.findViewById(R.id.keyguard_user_switcher), mHeader);
-
         mNetworkController.addSignalCluster(signalCluster);
         signalCluster.setNetworkController(mNetworkController);
         final boolean isAPhone = mNetworkController.hasVoiceCallingFeature();
@@ -769,6 +772,11 @@
         mUserSwitcherController = new UserSwitcherController(mContext);
         mKeyguardMonitor = new KeyguardMonitor();
 
+        mKeyguardUserSwitcher = new KeyguardUserSwitcher(mContext,
+                (ViewStub) mStatusBarWindow.findViewById(R.id.keyguard_user_switcher), mHeader,
+                mUserSwitcherController);
+
+
         // Set up the quick settings tile panel
         mQSPanel = (QSPanel) mStatusBarWindow.findViewById(R.id.quick_settings_panel);
         if (mQSPanel != null) {
@@ -1728,7 +1736,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 +2734,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 +2809,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 +3195,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 +3275,8 @@
 
     private void updatePublicMode() {
         setLockscreenPublicMode(
-                (mStatusBarKeyguardViewManager.isShowing() || 
-                    mStatusBarKeyguardViewManager.isOccluded())
+                (mStatusBarKeyguardViewManager.isShowing() ||
+                        mStatusBarKeyguardViewManager.isOccluded())
                 && mStatusBarKeyguardViewManager.isSecure());
     }
 
@@ -3315,7 +3386,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/StatusBarHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
index 2f88e21..1290cd1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
@@ -35,6 +35,7 @@
 import com.android.systemui.qs.QSPanel;
 import com.android.systemui.qs.QSTile;
 import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
 import com.android.systemui.statusbar.policy.UserInfoController;
 
 /**
@@ -97,6 +98,7 @@
     private ActivityStarter mActivityStarter;
     private BatteryController mBatteryController;
     private QSPanel mQSPanel;
+    private boolean mHasKeyguardUserSwitcher;
 
     private final Rect mClipBounds = new Rect();
     private final StatusIconClipper mStatusIconClipper = new StatusIconClipper();
@@ -373,7 +375,11 @@
     private void updateClickTargets() {
         setClickable(!mKeyguardShowing || mExpanded);
         mDateTime.setClickable(mExpanded);
-        mMultiUserSwitch.setClickable(mExpanded);
+
+        boolean keyguardSwitcherAvailable =
+                mHasKeyguardUserSwitcher && mKeyguardShowing && !mExpanded;
+        mMultiUserSwitch.setClickable(mExpanded || keyguardSwitcherAvailable);
+        mMultiUserSwitch.setKeyguardMode(keyguardSwitcherAvailable);
         mSystemIconsSuperContainer.setClickable(mExpanded);
     }
 
@@ -509,6 +515,11 @@
         mMultiUserSwitch.setQsPanel(qsp);
     }
 
+    public void setKeyguarUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) {
+        mHasKeyguardUserSwitcher = true;
+        mMultiUserSwitch.setKeyguardUserSwitcher(keyguardUserSwitcher);
+    }
+
     @Override
     public boolean shouldDelayChildPressedState() {
         return true;
@@ -522,7 +533,6 @@
     }
 
     public void setKeyguardUserSwitcherShowing(boolean showing) {
-        // STOPSHIP: NOT CALLED PROPERLY WHEN GOING TO FULL SHADE AND RETURNING!?!
         mKeyguardUserSwitcherShowing = showing;
         updateVisibilities();
         updateSystemIconsLayoutParams();
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();
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
index a3f3819..b0bab48 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
@@ -16,62 +16,56 @@
 
 package com.android.systemui.statusbar.policy;
 
-import com.android.systemui.BitmapHelper;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.phone.StatusBarHeaderView;
 import com.android.systemui.statusbar.phone.UserAvatarView;
 
-import android.app.ActivityManagerNative;
 import android.content.Context;
-import android.content.pm.UserInfo;
-import android.graphics.Bitmap;
-import android.os.AsyncTask;
-import android.os.RemoteException;
-import android.os.UserManager;
-import android.util.Log;
+import android.database.DataSetObserver;
+import android.provider.Settings;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewStub;
-import android.view.WindowManagerGlobal;
 import android.widget.TextView;
 
-import java.util.ArrayList;
-import java.util.List;
-
 /**
  * Manages the user switcher on the Keyguard.
  */
-public class KeyguardUserSwitcher implements View.OnClickListener {
+public class KeyguardUserSwitcher {
 
     private static final String TAG = "KeyguardUserSwitcher";
+    private static final boolean ALWAYS_ON = false;
+    private static final String SIMPLE_USER_SWITCHER_GLOBAL_SETTING =
+            "lockscreenSimpleUserSwitcher";
 
-    private final Context mContext;
     private final ViewGroup mUserSwitcher;
-    private final UserManager mUserManager;
     private final StatusBarHeaderView mHeader;
+    private final Adapter mAdapter;
+    private final boolean mSimpleUserSwitcher;
 
     public KeyguardUserSwitcher(Context context, ViewStub userSwitcher,
-            StatusBarHeaderView header) {
-        mContext = context;
-        if (context.getResources().getBoolean(R.bool.config_keyguardUserSwitcher)) {
+            StatusBarHeaderView header, UserSwitcherController userSwitcherController) {
+        if (context.getResources().getBoolean(R.bool.config_keyguardUserSwitcher) || ALWAYS_ON) {
             mUserSwitcher = (ViewGroup) userSwitcher.inflate();
-            mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
             mHeader = header;
-            refresh();
+            mHeader.setKeyguarUserSwitcher(this);
+            mAdapter = new Adapter(context, userSwitcherController);
+            mAdapter.registerDataSetObserver(mDataSetObserver);
+            mSimpleUserSwitcher = Settings.Global.getInt(context.getContentResolver(),
+                    SIMPLE_USER_SWITCHER_GLOBAL_SETTING, 0) != 0;
         } else {
             mUserSwitcher = null;
-            mUserManager = null;
             mHeader = null;
+            mAdapter = null;
+            mSimpleUserSwitcher = false;
         }
     }
 
     public void setKeyguard(boolean keyguard) {
         if (mUserSwitcher != null) {
-            // TODO: Cache showUserSwitcherOnKeyguard().
-            if (keyguard && showUserSwitcherOnKeyguard()) {
+            if (keyguard && shouldExpandByDefault()) {
                 show();
-                refresh();
             } else {
                 hide();
             }
@@ -79,24 +73,11 @@
     }
 
     /**
-     * @return true if the user switcher should be shown on the lock screen.
+     * @return true if the user switcher should be expanded by default on the lock screen.
      * @see android.os.UserManager#isUserSwitcherEnabled()
      */
-    private boolean showUserSwitcherOnKeyguard() {
-        // TODO: Set isEdu. The edu provisioning process can add settings to Settings.Global.
-        boolean isEdu = false;
-        if (isEdu) {
-            return true;
-        }
-        List<UserInfo> users = mUserManager.getUsers(true /* excludeDying */);
-        int N = users.size();
-        int switchableUsers = 0;
-        for (int i = 0; i < N; i++) {
-            if (users.get(i).supportsSwitchTo()) {
-                switchableUsers++;
-            }
-        }
-        return switchableUsers > 1;
+    private boolean shouldExpandByDefault() {
+        return mSimpleUserSwitcher || mAdapter.getSwitchableUsers() > 1;
     }
 
     public void show() {
@@ -116,100 +97,76 @@
     }
 
     private void refresh() {
-        if (mUserSwitcher != null) {
-            new AsyncTask<Void, Void, ArrayList<UserData>>() {
-                @Override
-                protected ArrayList<UserData> doInBackground(Void... params) {
-                    return loadUsers();
-                }
-
-                @Override
-                protected void onPostExecute(ArrayList<UserData> userInfos) {
-                    bind(userInfos);
-                }
-            }.execute((Void[]) null);
-        }
-    }
-
-    private void bind(ArrayList<UserData> userList) {
-        mUserSwitcher.removeAllViews();
-        int N = userList.size();
+        final int childCount = mUserSwitcher.getChildCount();
+        final int adapterCount = mAdapter.getCount();
+        final int N = Math.max(childCount, adapterCount);
         for (int i = 0; i < N; i++) {
-            mUserSwitcher.addView(inflateUser(userList.get(i)));
-        }
-        // TODO: add Guest
-        // TODO: add (+) button
-    }
-
-    private View inflateUser(UserData user) {
-        View v = LayoutInflater.from(mUserSwitcher.getContext()).inflate(
-                R.layout.keyguard_user_switcher_item, mUserSwitcher, false);
-        TextView name = (TextView) v.findViewById(R.id.name);
-        UserAvatarView picture = (UserAvatarView) v.findViewById(R.id.picture);
-        name.setText(user.userInfo.name);
-        picture.setActivated(user.isCurrent);
-        if (user.userInfo.isGuest()) {
-            picture.setDrawable(mContext.getResources().getDrawable(R.drawable.ic_account_circle));
-        } else {
-            picture.setBitmap(user.userIcon);
-        }
-        v.setOnClickListener(this);
-        v.setTag(user.userInfo);
-        // TODO: mark which user is current for accessibility.
-        return v;
-    }
-
-    @Override
-    public void onClick(View v) {
-        switchUser(((UserInfo)v.getTag()).id);
-    }
-
-    // TODO: Factor out logic below and share with QS implementation.
-
-    private ArrayList<UserData> loadUsers() {
-        ArrayList<UserInfo> users = (ArrayList<UserInfo>) mUserManager
-                .getUsers(true /* excludeDying */);
-        int N = users.size();
-        ArrayList<UserData> result = new ArrayList<>(N);
-        int currentUser = -1;
-        try {
-            currentUser = ActivityManagerNative.getDefault().getCurrentUser().id;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Couln't get current user.", e);
-        }
-        final int avatarSize
-                = mContext.getResources().getDimensionPixelSize(R.dimen.max_avatar_size);
-        for (int i = 0; i < N; i++) {
-            UserInfo user = users.get(i);
-            if (user.supportsSwitchTo()) {
-                boolean isCurrent = user.id == currentUser;
-                final Bitmap picture = BitmapHelper.createCircularClip(
-                        mUserManager.getUserIcon(user.id),
-                        avatarSize, avatarSize);
-                result.add(new UserData(user, picture, isCurrent));
+            if (i < adapterCount) {
+                View oldView = null;
+                if (i < childCount) {
+                    oldView = mUserSwitcher.getChildAt(i);
+                }
+                View newView = mAdapter.getView(i, oldView, mUserSwitcher);
+                if (oldView == null) {
+                    // We ran out of existing views. Add it at the end.
+                    mUserSwitcher.addView(newView);
+                } else if (oldView != newView) {
+                    // We couldn't rebind the view. Replace it.
+                    mUserSwitcher.removeViewAt(i);
+                    mUserSwitcher.addView(newView, i);
+                }
+            } else {
+                int lastIndex = mUserSwitcher.getChildCount() - 1;
+                mUserSwitcher.removeViewAt(lastIndex);
             }
         }
-        return result;
     }
 
-    private void switchUser(int userId) {
-        try {
-            WindowManagerGlobal.getWindowManagerService().lockNow(null);
-            ActivityManagerNative.getDefault().switchUser(userId);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Couldn't switch user.", e);
+    public final DataSetObserver mDataSetObserver = new DataSetObserver() {
+        @Override
+        public void onChanged() {
+            refresh();
         }
-    }
+    };
 
-    private static class UserData {
-        final UserInfo userInfo;
-        final Bitmap userIcon;
-        final boolean isCurrent;
+    public static class Adapter extends UserSwitcherController.BaseUserAdapter implements
+            View.OnClickListener {
 
-        UserData(UserInfo userInfo, Bitmap userIcon, boolean isCurrent) {
-            this.userInfo = userInfo;
-            this.userIcon = userIcon;
-            this.isCurrent = isCurrent;
+        private Context mContext;
+
+        public Adapter(Context context, UserSwitcherController controller) {
+            super(controller);
+            mContext = context;
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            UserSwitcherController.UserRecord item = getItem(position);
+
+            if (convertView == null
+                    || !(convertView.getTag() instanceof UserSwitcherController.UserRecord)) {
+                convertView = LayoutInflater.from(mContext).inflate(
+                        R.layout.keyguard_user_switcher_item, parent, false);
+                convertView.setOnClickListener(this);
+            }
+
+            TextView nameView = (TextView) convertView.findViewById(R.id.name);
+            UserAvatarView pictureView = (UserAvatarView) convertView.findViewById(R.id.picture);
+
+            nameView.setText(getName(mContext, item));
+            if (item.picture == null) {
+                pictureView.setDrawable(mContext.getDrawable(R.drawable.ic_account_circle_qs));
+            } else {
+                pictureView.setBitmap(item.picture);
+            }
+            convertView.setActivated(item.isCurrent);
+            convertView.setTag(item);
+            return convertView;
+        }
+
+        @Override
+        public void onClick(View v) {
+            switchTo(((UserSwitcherController.UserRecord)v.getTag()));
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 7bf2c34..7c00c7f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -123,7 +123,7 @@
                     if (info.isGuest()) {
                         guestRecord = new UserRecord(info, null /* picture */,
                                 true /* isGuest */, isCurrent);
-                    } else if (!info.isManagedProfile()) {
+                    } else if (info.supportsSwitchTo()) {
                         Bitmap picture = bitmaps.get(info.id);
                         if (picture == null) {
                             picture = mUserManager.getUserIcon(info.id);
@@ -303,6 +303,18 @@
                 return item.info.name;
             }
         }
+
+        public int getSwitchableUsers() {
+            int result = 0;
+            ArrayList<UserRecord> users = mController.mUsers;
+            int N = users.size();
+            for (int i = 0; i < N; i++) {
+                if (users.get(i).info != null) {
+                    result++;
+                }
+            }
+            return result;
+        }
     }
 
     public static final class UserRecord {
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 39bbf72..35568cf 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -3322,4 +3322,23 @@
         }
         return false;
     }
+
+    /**
+     * Returns the result and response from RIL for oem request
+     *
+     * @param oemReq the data is sent to ril.
+     * @param oemResp the respose data from RIL.
+     * @return negative value request was not handled or get error
+     *         0 request was handled succesfully, but no response data
+     *         positive value success, data length of response
+     * @hide
+     */
+    public int invokeOemRilRequestRaw(byte[] oemReq, byte[] oemResp) {
+        try {
+            return getITelephony().invokeOemRilRequestRaw(oemReq, oemResp);
+        } catch (RemoteException ex) {
+        } catch (NullPointerException ex) {
+        }
+        return -1;
+    }
 }
diff --git a/telephony/java/com/android/ims/internal/IImsRegistrationListener.aidl b/telephony/java/com/android/ims/internal/IImsRegistrationListener.aidl
index 5f243a0..1413e58 100644
--- a/telephony/java/com/android/ims/internal/IImsRegistrationListener.aidl
+++ b/telephony/java/com/android/ims/internal/IImsRegistrationListener.aidl
@@ -55,4 +55,15 @@
      *    Else ({@code event} is 1), meaning the specified service is added to the IMS connection.
      */
     void registrationServiceCapabilityChanged(int serviceClass, int event);
+
+    /**
+     * Notifies the application when features on a particular service enabled or
+     * disabled successfully based on user preferences.
+     *
+     * @param serviceClass a service class specified in {@link ImsServiceClass}
+     * @param enabledFeatures features enabled as defined in com.android.ims.ImsConfig#FeatureConstants.
+     * @param disabledFeatures features disabled as defined in com.android.ims.ImsConfig#FeatureConstants.
+     */
+    void registrationFeatureCapabilityChanged(int serviceClass,
+            out int[] enabledFeatures, out int[] disabledFeatures);
 }
diff --git a/telephony/java/com/android/ims/internal/IImsService.aidl b/telephony/java/com/android/ims/internal/IImsService.aidl
index d992124..869cd9f 100644
--- a/telephony/java/com/android/ims/internal/IImsService.aidl
+++ b/telephony/java/com/android/ims/internal/IImsService.aidl
@@ -51,4 +51,15 @@
      * Config interface to get/set IMS service/capability parameters.
      */
     IImsConfig getConfigInterface();
+
+    /**
+     * Used for turning on IMS when its in OFF state.
+     */
+    void turnOnIms();
+
+    /**
+     * Used for turning off IMS when its in ON state.
+     * When IMS is OFF, device will behave as CSFB'ed.
+     */
+    void turnOffIms();
 }
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 8c37e3d..886de40 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -737,5 +737,16 @@
      * @return true if the operation was executed correctly.
      */
     boolean setOperatorBrandOverride(String iccId, String brand);
+
+    /**
+     * Returns the result and response from RIL for oem request
+     *
+     * @param oemReq the data is sent to ril.
+     * @param oemResp the respose data from RIL.
+     * @return negative value request was not handled or get error
+     *         0 request was handled succesfully, but no response data
+     *         positive value success, data length of response
+     */
+    int invokeOemRilRequestRaw(in byte[] oemReq, out byte[] oemResp);
 }