am c01d1045: Merge "Fix for bug 7400445 regression in background drawable" into jb-mr1-dev

* commit 'c01d1045ef6c79000afc6f07a74ee7963a2092ac':
  Fix for bug 7400445 regression in background drawable
diff --git a/core/res/res/layout-port/keyguard_host_view.xml b/core/res/res/layout-port/keyguard_host_view.xml
index 5444471..a3c8105 100644
--- a/core/res/res/layout-port/keyguard_host_view.xml
+++ b/core/res/res/layout-port/keyguard_host_view.xml
@@ -36,7 +36,8 @@
 
         <FrameLayout
             android:layout_width="match_parent"
-            android:layout_height="match_parent">
+            android:layout_height="match_parent"
+            androidprv:layout_childType="widgets">
             <include layout="@layout/keyguard_widget_pager"
                 android:id="@+id/app_widget_container"
                 android:layout_width="match_parent"
@@ -51,12 +52,11 @@
 
         <com.android.internal.policy.impl.keyguard.KeyguardSecurityContainer
             android:id="@+id/keyguard_security_container"
-            android:layout_width="@dimen/keyguard_security_width"
+            android:layout_width="wrap_content"
             android:layout_height="@dimen/keyguard_security_height"
             androidprv:layout_childType="challenge"
-            android:layout_marginLeft="@dimen/kg_edge_swipe_region_size"
-            android:layout_marginRight="@dimen/kg_edge_swipe_region_size"
             android:background="@drawable/kg_bouncer_bg_white"
+            android:padding="0dp"
             android:gravity="bottom|center_horizontal">
             <com.android.internal.policy.impl.keyguard.KeyguardSecurityViewFlipper
                 android:id="@+id/view_flipper"
@@ -64,9 +64,7 @@
                 android:layout_height="match_parent"
                 android:clipChildren="false"
                 android:clipToPadding="false"
-                android:paddingLeft="@dimen/keyguard_security_view_margin"
                 android:paddingTop="@dimen/keyguard_security_view_margin"
-                android:paddingRight="@dimen/keyguard_security_view_margin"
                 android:gravity="center">
             </com.android.internal.policy.impl.keyguard.KeyguardSecurityViewFlipper>
         </com.android.internal.policy.impl.keyguard.KeyguardSecurityContainer>
diff --git a/core/res/res/layout/keyguard_pin_view.xml b/core/res/res/layout/keyguard_pin_view.xml
index 19e0a27..2529196 100644
--- a/core/res/res/layout/keyguard_pin_view.xml
+++ b/core/res/res/layout/keyguard_pin_view.xml
@@ -59,6 +59,7 @@
             android:paddingLeft="24dp"
             android:paddingRight="24dp"
             android:background="?android:attr/selectableItemBackground"
+            android:contentDescription="@string/keyboardview_keycode_delete"
             />
     </LinearLayout>
     <View
@@ -196,6 +197,7 @@
             android:layout_height="match_parent"
             android:layout_weight="1"
             android:src="@drawable/sym_keyboard_return_holo"
+            android:contentDescription="@string/keyboardview_keycode_enter"
             />
     </LinearLayout>
 
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index d186c4a..627099c 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -5798,6 +5798,8 @@
         <!-- Scrim. This will block access to child views that
              come before it in the child list in bouncer mode. -->
         <enum name="scrim" value="4" />
+        <!-- The home for widgets. All widgets will be descendents of this. -->
+        <enum name="widgets" value="5" />
     </attr>
 
     <declare-styleable name="SlidingChallengeLayout">
diff --git a/core/res/res/values/integers.xml b/core/res/res/values/integers.xml
index d1df2a4..053fc85 100644
--- a/core/res/res/values/integers.xml
+++ b/core/res/res/values/integers.xml
@@ -18,7 +18,7 @@
 -->
 <resources>
     <integer name="kg_carousel_angle">75</integer>
-    <integer name="kg_security_flip_duration">75</integer>
-    <integer name="kg_security_fade_duration">75</integer>
+    <integer name="kg_security_flip_duration">100</integer>
+    <integer name="kg_security_fade_duration">100</integer>
     <integer name="kg_glowpad_rotation_offset">0</integer>
 </resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 159eec1..4a4f1c4 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2276,14 +2276,12 @@
     <string name="keyguard_accessibility_add_widget">Add widget.</string>
     <!-- Accessibility description of the empty sidget slot (place holder for a new widget). [CHAR_LIMIT=none] -->
     <string name="keyguard_accessibility_widget_empty_slot">Empty</string>
-    <!-- Accessibility description of the unlock area. [CHAR_LIMIT=none] -->
-    <string name="keyguard_accessibility_unlock_area">Unlock area.</string>
     <!-- Accessibility description of the event of expanding an unlock area. [CHAR_LIMIT=none] -->
     <string name="keyguard_accessibility_unlock_area_expanded">Unlock area expanded.</string>
     <!-- Accessibility description of the event of collapsing an unlock area. [CHAR_LIMIT=none] -->
     <string name="keyguard_accessibility_unlock_area_collapsed">Unlock area collapsed.</string>
     <!-- Accessibility description of a lock screen widget. [CHAR_LIMIT=none] -->
-    <string name="keyguard_accessibility_widget">%1$s widget.</string>
+    <string name="keyguard_accessibility_widget"><xliff:g id="widget_index">%1$s</xliff:g> widget.</string>
     <!-- Accessibility description of the lock screen user selector widget. [CHAR_LIMIT=none] -->
     <string name="keyguard_accessibility_user_selector">User selector</string>
     <!-- Accessibility description of the lock screen status widget. [CHAR_LIMIT=none] -->
@@ -2292,6 +2290,28 @@
     <string name="keyguard_accessibility_camera">Camera</string>
     <!-- Accessibility description of the lock media control widget. [CHAR_LIMIT=none] -->
     <string name="keygaurd_accessibility_media_controls">Media controls</string>
+    <!-- Accessibility description of widget reordering start. [CHAR_LIMIT=none] -->
+    <string name="keyguard_accessibility_widget_reorder_start">Widget reordering started.</string>
+    <!-- Accessibility description of widget reordering end. [CHAR_LIMIT=none] -->
+    <string name="keyguard_accessibility_widget_reorder_end">Widget reordering ended.</string>
+    <!-- Accessibility description of the a widget deletion event. [CHAR_LIMIT=none] -->
+    <string name="keyguard_accessibility_widget_deleted">Widget <xliff:g id="widget_index">%1$s</xliff:g> deleted.</string>
+    <!-- Accessibility description of the button to expand the lock area. [CHAR_LIMIT=none] -->
+    <string name="keyguard_accessibility_expand_lock_area">Expand unlock area.</string>
+    <!-- Accessibility description of the slide unlock. [CHAR_LIMIT=none] -->
+    <string name="keyguard_accessibility_slide_unlock">Slide unlock.</string>
+    <!-- Accessibility description of the pattern unlock. [CHAR_LIMIT=none] -->
+    <string name="keyguard_accessibility_pattern_unlock">Pattern unlock.</string>
+    <!-- Accessibility description of the face unlock. [CHAR_LIMIT=none] -->
+    <string name="keyguard_accessibility_face_unlock">Face unlock.</string>
+    <!-- Accessibility description of the pin lock. [CHAR_LIMIT=none] -->
+    <string name="keyguard_accessibility_pin_unlock">Pin unlock.</string>
+    <!-- Accessibility description of the password lock. [CHAR_LIMIT=none] -->
+    <string name="keyguard_accessibility_password_unlock">Password unlock.</string>
+    <!-- Accessibility description of the unlock pattern area. [CHAR_LIMIT=none] -->
+    <string name="keyguard_accessibility_pattern_area">Pattern area.</string>
+    <!-- Accessibility description of the unlock slide area. [CHAR_LIMIT=none] -->
+    <string name="keyguard_accessibility_slide_area">Slide area.</string>
 
     <!-- Password keyboard strings. Used by LockScreen and Settings --><skip />
     <!-- Label for "switch to symbols" key.  Must be short to fit on key! -->
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardAbsKeyInputView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardAbsKeyInputView.java
index eabf5e0..db36bcc 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardAbsKeyInputView.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardAbsKeyInputView.java
@@ -98,6 +98,9 @@
         mPasswordEntry.setOnEditorActionListener(this);
         mPasswordEntry.addTextChangedListener(this);
 
+        // Set selected property on so the view can send accessibility events.
+        mPasswordEntry.setSelected(true);
+
         // Poke the wakelock any time the text is selected or modified
         mPasswordEntry.setOnClickListener(new OnClickListener() {
             public void onClick(View v) {
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java
index e929fb1..96cd7ac 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java
@@ -670,13 +670,10 @@
         // Find and show this child.
         final int childCount = mSecurityViewContainer.getChildCount();
 
-        // Do flip animation to the next screen
-        if (false) {
-            mSecurityViewContainer.setInAnimation(
-                    AnimationUtils.loadAnimation(mContext, R.anim.keyguard_security_animate_in));
-            mSecurityViewContainer.setOutAnimation(
-                    AnimationUtils.loadAnimation(mContext, R.anim.keyguard_security_animate_out));
-        }
+        mSecurityViewContainer.setInAnimation(
+                AnimationUtils.loadAnimation(mContext, R.anim.keyguard_security_fade_in));
+        mSecurityViewContainer.setOutAnimation(
+                AnimationUtils.loadAnimation(mContext, R.anim.keyguard_security_fade_out));
         final int securityViewIdForMode = getSecurityViewIdForMode(securityMode);
         for (int i = 0; i < childCount; i++) {
             if (mSecurityViewContainer.getChildAt(i).getId() == securityViewIdForMode) {
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardMessageArea.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardMessageArea.java
index ca78cf9..5e331e1 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardMessageArea.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardMessageArea.java
@@ -16,6 +16,9 @@
 
 package com.android.internal.policy.impl.keyguard;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.os.Handler;
@@ -39,11 +42,15 @@
     static final int BATTERY_LOW_ICON = 0; //R.drawable.ic_lock_idle_low_battery;
 
     static final int SECURITY_MESSAGE_DURATION = 5000;
-    static final String SEPARATOR = " ";
+    protected static final int FADE_DURATION = 750;
+    static final String SEPARATOR = "  ";
 
     // are we showing battery information?
     boolean mShowingBatteryInfo = false;
 
+    // is the bouncer up?
+    boolean mShowingBouncer = false;
+
     // last known plugged in state
     boolean mPluggedIn = false;
 
@@ -68,7 +75,11 @@
         public void run() {
             mMessage = null;
             mShowingMessage = false;
-            update();
+            if (mShowingBouncer) {
+                hideMessage(FADE_DURATION, true);
+            } else {
+                update();
+            }
         }
     };
 
@@ -103,6 +114,18 @@
         }
 
         @Override
+        public void showBouncer(int duration) {
+            mMessageArea.hideMessage(duration, false);
+            mMessageArea.mShowingBouncer = true;
+        }
+
+        @Override
+        public void hideBouncer(int duration) {
+            mMessageArea.showMessage(duration);
+            mMessageArea.mShowingBouncer = false;
+        }
+
+        @Override
         public void setTimeout(int timeoutMs) {
             mMessageArea.mTimeout = timeoutMs;
         }
@@ -139,6 +162,7 @@
     }
 
     public void securityMessageChanged() {
+        setAlpha(1f);
         mShowingMessage = true;
         update();
         mHandler.removeCallbacks(mClearMessageRunnable);
@@ -212,4 +236,23 @@
         return string;
     }
 
+    private void hideMessage(int duration, boolean thenUpdate) {
+        Animator anim = ObjectAnimator.ofFloat(this, "alpha", 0f);
+        anim.setDuration(duration);
+        if (thenUpdate) {
+            anim.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                        public void onAnimationEnd(Animator animation) {
+                        update();
+                    }
+                });
+        }
+        anim.start();
+    }
+
+    private void showMessage(int duration) {
+        Animator anim = ObjectAnimator.ofFloat(this, "alpha", 1f);
+        anim.setDuration(duration);
+        anim.start();
+    }
 }
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardPINView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardPINView.java
index 8522401..1255712 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardPINView.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardPINView.java
@@ -64,6 +64,7 @@
                     verifyPasswordAndUnlock();
                 }
             });
+            ok.setOnHoverListener(new NumPadKey.LiftToActivateListener(getContext()));
         }
 
         // The delete button is of the PIN keyboard itself in some (e.g. tablet) layouts,
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityContainer.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityContainer.java
index f6a90c5..04ab0a2 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityContainer.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityContainer.java
@@ -1,11 +1,20 @@
 package com.android.internal.policy.impl.keyguard;
 
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
 import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.widget.FrameLayout;
 
+import com.android.internal.R;
+
 public class KeyguardSecurityContainer extends FrameLayout {
 
+    private float mBackgroundAlpha;
+    private Drawable mBackgroundDrawable;
+
     public KeyguardSecurityContainer(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
     }
@@ -16,5 +25,44 @@
 
     public KeyguardSecurityContainer(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
+        mBackgroundDrawable = context.getResources().getDrawable(R.drawable.kg_bouncer_bg_white);
+    }
+
+    public void setBackgroundAlpha(float alpha) {
+        if (Float.compare(mBackgroundAlpha, alpha) != 0) {
+            mBackgroundAlpha = alpha;
+            invalidate();
+        }
+    }
+
+    public float getBackgroundAlpha() {
+        return mBackgroundAlpha;
+    }
+
+    @Override
+    protected void dispatchDraw(Canvas canvas) {
+        if (mBackgroundAlpha > 0.0f && mBackgroundDrawable != null) {
+            Drawable bg = mBackgroundDrawable;
+            bg.setAlpha((int) (mBackgroundAlpha * 255));
+            bg.setBounds(0, 0, getMeasuredWidth(), getMeasuredHeight());
+            bg.draw(canvas);
+        }
+        super.dispatchDraw(canvas);
+    }
+
+    public void showBouncer(int duration) {
+        SecurityMessageDisplay message = new KeyguardMessageArea.Helper(this);
+        message.showBouncer(duration);
+        Animator anim = ObjectAnimator.ofFloat(this, "BackgroundAlpha", 1f);
+        anim.setDuration(duration);
+        anim.start();
+    }
+
+    public void hideBouncer(int duration) {
+        SecurityMessageDisplay message = new KeyguardMessageArea.Helper(this);
+        message.hideBouncer(duration);
+        Animator anim = ObjectAnimator.ofFloat(this, "BackgroundAlpha", 0f);
+        anim.setDuration(duration);
+        anim.start();
     }
 }
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardStatusViewManager.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardStatusViewManager.java
index b4bd6e9..7100f1c 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardStatusViewManager.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardStatusViewManager.java
@@ -327,6 +327,14 @@
     }
 
     @Override
+    public void showBouncer(int duration) {
+    }
+
+    @Override
+    public void hideBouncer(int duration) {
+    }
+
+    @Override
     public void setTimeout(int timeout_ms) {
     }
 
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/MultiPaneChallengeLayout.java b/policy/src/com/android/internal/policy/impl/keyguard/MultiPaneChallengeLayout.java
index a207f5d..b38eb28 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/MultiPaneChallengeLayout.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/MultiPaneChallengeLayout.java
@@ -16,6 +16,10 @@
 
 package com.android.internal.policy.impl.keyguard;
 
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Rect;
@@ -35,8 +39,9 @@
 
     public static final int HORIZONTAL = LinearLayout.HORIZONTAL;
     public static final int VERTICAL = LinearLayout.VERTICAL;
+    protected static final int ANIMATE_BOUNCE_DURATION = 750;
 
-    private View mChallengeView;
+    private KeyguardSecurityContainer mChallengeView;
     private View mUserSwitcherView;
     private View mScrimView;
     private OnBouncerStateChangedListener mBouncerListener;
@@ -87,7 +92,19 @@
         if (mIsBouncing) return;
         mIsBouncing = true;
         if (mScrimView != null) {
-            mScrimView.setVisibility(GONE);
+            if (mChallengeView != null) {
+                mChallengeView.showBouncer(ANIMATE_BOUNCE_DURATION);
+            }
+
+            Animator anim = ObjectAnimator.ofFloat(mScrimView, "alpha", 1f);
+            anim.setDuration(ANIMATE_BOUNCE_DURATION);
+            anim.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationStart(Animator animation) {
+                    mScrimView.setVisibility(VISIBLE);
+                }
+            });
+            anim.start();
         }
         if (mBouncerListener != null) {
             mBouncerListener.onBouncerStateChanged(true);
@@ -99,7 +116,19 @@
         if (!mIsBouncing) return;
         mIsBouncing = false;
         if (mScrimView != null) {
-            mScrimView.setVisibility(GONE);
+            if (mChallengeView != null) {
+                mChallengeView.hideBouncer(ANIMATE_BOUNCE_DURATION);
+            }
+
+            Animator anim = ObjectAnimator.ofFloat(mScrimView, "alpha", 0f);
+            anim.setDuration(ANIMATE_BOUNCE_DURATION);
+            anim.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mScrimView.setVisibility(INVISIBLE);
+                }
+            });
+            anim.start();
         }
         if (mBouncerListener != null) {
             mBouncerListener.onBouncerStateChanged(false);
@@ -131,7 +160,8 @@
             mScrimView.setOnClickListener(null);
         }
         mScrimView = scrim;
-        mScrimView.setVisibility(mIsBouncing ? VISIBLE : GONE);
+        mScrimView.setAlpha(mIsBouncing ? 1.0f : 0.0f);
+        mScrimView.setVisibility(mIsBouncing ? VISIBLE : INVISIBLE);
         mScrimView.setFocusable(true);
         mScrimView.setOnClickListener(mScrimClickListener);
     }
@@ -165,7 +195,11 @@
                     throw new IllegalStateException(
                             "There may only be one child of type challenge");
                 }
-                mChallengeView = child;
+                if (!(child instanceof KeyguardSecurityContainer)) {
+                    throw new IllegalArgumentException(
+                            "Challenge must be a KeyguardSecurityContainer");
+                }
+                mChallengeView = (KeyguardSecurityContainer) child;
             } else if (lp.childType == LayoutParams.CHILD_TYPE_USER_SWITCHER) {
                 if (mUserSwitcherView != null) {
                     throw new IllegalStateException(
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/NumPadKey.java b/policy/src/com/android/internal/policy/impl/keyguard/NumPadKey.java
index 060cc03..ca36007 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/NumPadKey.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/NumPadKey.java
@@ -22,7 +22,9 @@
 import android.text.style.TextAppearanceSpan;
 import android.util.AttributeSet;
 import android.view.HapticFeedbackConstants;
+import android.view.MotionEvent;
 import android.view.View;
+import android.view.accessibility.AccessibilityManager;
 import android.widget.Button;
 import android.widget.TextView;
 
@@ -72,6 +74,7 @@
         setTextViewResId(a.getResourceId(R.styleable.NumPadKey_textView, 0));
 
         setOnClickListener(mListener);
+        setOnHoverListener(new LiftToActivateListener(context));
 
         mEnableHaptics = new LockPatternUtils(context).isTactileFeedbackEnabled();
 
@@ -113,4 +116,45 @@
                     | HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
         }
     }
+
+    /**
+     * Hover listener that implements lift-to-activate interaction for
+     * accessibility. May be added to multiple views.
+     */
+    static class LiftToActivateListener implements View.OnHoverListener {
+        /** Manager used to query accessibility enabled state. */
+        private final AccessibilityManager mAccessibilityManager;
+
+        public LiftToActivateListener(Context context) {
+            mAccessibilityManager = (AccessibilityManager) context.getSystemService(
+                    Context.ACCESSIBILITY_SERVICE);
+        }
+
+        @Override
+        public boolean onHover(View v, MotionEvent event) {
+            // When touch exploration is turned on, lifting a finger while
+            // inside the view bounds should perform a click action.
+            if (mAccessibilityManager.isEnabled()
+                    && mAccessibilityManager.isTouchExplorationEnabled()) {
+                switch (event.getActionMasked()) {
+                    case MotionEvent.ACTION_HOVER_ENTER:
+                        // Lift-to-type temporarily disables double-tap
+                        // activation.
+                        v.setClickable(false);
+                        break;
+                    case MotionEvent.ACTION_HOVER_EXIT:
+                        final int x = (int) event.getX();
+                        final int y = (int) event.getY();
+                        if ((x > v.getPaddingLeft()) && (y > v.getPaddingTop())
+                                && (x < v.getWidth() - v.getPaddingRight())
+                                && (y < v.getHeight() - v.getPaddingBottom())) {
+                            v.performClick();
+                        }
+                        v.setClickable(true);
+                        break;
+                }
+            }
+            return false;
+        }
+    }
 }
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/SecurityMessageDisplay.java b/policy/src/com/android/internal/policy/impl/keyguard/SecurityMessageDisplay.java
index ec6472f..7760279 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/SecurityMessageDisplay.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/SecurityMessageDisplay.java
@@ -24,4 +24,8 @@
     public void setMessage(int resId, boolean important, Object... formatArgs);
 
     public void setTimeout(int timeout_ms);
+
+    public void showBouncer(int animationDuration);
+
+    public void hideBouncer(int animationDuration);
 }
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/SlidingChallengeLayout.java b/policy/src/com/android/internal/policy/impl/keyguard/SlidingChallengeLayout.java
index 74b05dd..7cf995c 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/SlidingChallengeLayout.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/SlidingChallengeLayout.java
@@ -16,11 +16,14 @@
 
 package com.android.internal.policy.impl.keyguard;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.Paint;
+import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.util.FloatProperty;
@@ -61,10 +64,12 @@
     private Drawable mHandleDrawable;
     private Drawable mFrameDrawable;
     private Drawable mDragIconDrawable;
+    private boolean mEdgeCaptured;
 
     // Initialized during measurement from child layoutparams
     private View mChallengeView;
     private View mScrimView;
+    private View mWidgetsView;
 
     // Range: 0 (fully hidden) to 1 (fully visible)
     private float mChallengeOffset = 1.f;
@@ -110,9 +115,14 @@
 
     float mHandleAlpha;
     float mFrameAlpha;
+    float mFrameAnimationTarget = Float.MIN_VALUE;
     private ObjectAnimator mHandleAnimation;
     private ObjectAnimator mFrameAnimation;
 
+    private final Rect mTempRect = new Rect();
+
+    private boolean mHasGlowpad;
+
     static final Property<SlidingChallengeLayout, Float> HANDLE_ALPHA =
             new FloatProperty<SlidingChallengeLayout>("handleAlpha") {
         @Override
@@ -293,21 +303,43 @@
         mHandleAnimation.start();
     }
 
-    void animateFrame(boolean visible, boolean full) {
+    void animateFrame(final boolean visible, final boolean full) {
         if (mFrameDrawable == null) return;
 
-        if (mFrameAnimation != null) {
-            mFrameAnimation.cancel();
-            mFrameAnimation = null;
-        }
         final float targetAlpha = visible ? (full ? 1.f : 0.5f) : 0.f;
-        if (targetAlpha == mFrameAlpha) {
+        if (mFrameAnimation != null && targetAlpha != mFrameAnimationTarget) {
+            mFrameAnimation.cancel();
+            mFrameAnimationTarget = Float.MIN_VALUE;
+        }
+        if (targetAlpha == mFrameAlpha || targetAlpha == mFrameAnimationTarget) {
             return;
         }
+        mFrameAnimationTarget = targetAlpha;
 
         mFrameAnimation = ObjectAnimator.ofFloat(this, FRAME_ALPHA, targetAlpha);
         mFrameAnimation.setInterpolator(sHandleFadeInterpolator);
         mFrameAnimation.setDuration(HANDLE_ANIMATE_DURATION);
+        mFrameAnimation.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mFrameAnimationTarget = Float.MIN_VALUE;
+
+                if (!visible && full && mChallengeView != null) {
+                    // Mess with padding/margin to remove insets on the bouncer frame.
+                    mChallengeView.setPadding(0, 0, 0, 0);
+                    LayoutParams lp = (LayoutParams) mChallengeView.getLayoutParams();
+                    lp.leftMargin = lp.rightMargin = getChallengeMargin(true);
+                    mChallengeView.setLayoutParams(lp);
+                }
+                mFrameAnimation = null;
+            }
+
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                mFrameAnimationTarget = Float.MIN_VALUE;
+                mFrameAnimation = null;
+            }
+        });
         mFrameAnimation.start();
     }
 
@@ -370,7 +402,9 @@
             mScrollState = state;
 
             animateHandle(state == SCROLL_STATE_IDLE && !mChallengeShowing);
-            animateFrame(false , false);
+            if (!mIsBouncing) {
+                animateFrame(false, false);
+            }
             if (mScrollListener != null) {
                 mScrollListener.onScrollStateChanged(state);
             }
@@ -380,6 +414,7 @@
     void completeChallengeScroll() {
         setChallengeShowing(mChallengeOffset != 0);
         setScrollState(SCROLL_STATE_IDLE);
+        mChallengeView.setLayerType(LAYER_TYPE_NONE, null);
     }
 
     void setScrimView(View scrim) {
@@ -461,7 +496,22 @@
         if (mScrimView != null) {
             mScrimView.setVisibility(VISIBLE);
         }
+
+        // Mess with padding/margin to inset the bouncer frame.
+        // We have more space available to us otherwise.
+        if (mChallengeView != null) {
+            if (mFrameDrawable == null || !mFrameDrawable.getPadding(mTempRect)) {
+                mTempRect.set(0, 0, 0, 0);
+            }
+            mChallengeView.setPadding(mTempRect.left, mTempRect.top, mTempRect.right,
+                    mTempRect.bottom);
+            final LayoutParams lp = (LayoutParams) mChallengeView.getLayoutParams();
+            lp.leftMargin = lp.rightMargin = getChallengeMargin(false);
+            mChallengeView.setLayoutParams(lp);
+        }
+
         animateFrame(true, true);
+
         if (mBouncerListener != null) {
             mBouncerListener.onBouncerStateChanged(true);
         }
@@ -470,17 +520,21 @@
     @Override
     public void hideBouncer() {
         if (!mIsBouncing) return;
-        setChallengeShowing(false);
+        showChallenge(false);
         mIsBouncing = false;
         if (mScrimView != null) {
             mScrimView.setVisibility(GONE);
         }
-        animateFrame(false, false);
+        animateFrame(false, true);
         if (mBouncerListener != null) {
             mBouncerListener.onBouncerStateChanged(false);
         }
     }
 
+    private int getChallengeMargin(boolean expanded) {
+        return expanded && mHasGlowpad ? 0 : mDragHandleEdgeSlop;
+    }
+
     @Override
     public void requestDisallowInterceptTouchEvent(boolean allowIntercept) {
         // We'll intercept whoever we feel like! ...as long as it isn't a challenge view.
@@ -495,8 +549,6 @@
         }
         mVelocityTracker.addMovement(ev);
 
-        //Log.v(TAG, "onIntercept: " + ev);
-
         final int action = ev.getActionMasked();
         switch (action) {
             case MotionEvent.ACTION_DOWN:
@@ -525,6 +577,7 @@
                         mGestureStartY = y;
                         mGestureStartChallengeBottom = getChallengeBottom();
                         mDragging = true;
+                        mChallengeView.setLayerType(LAYER_TYPE_HARDWARE, null);
                     } else if (isInChallengeView(x, y)) {
                         mBlockDrag = true;
                     }
@@ -601,6 +654,7 @@
                             mActivePointerId = ev.getPointerId(i);
                             mGestureStartChallengeBottom = getChallengeBottom();
                             mDragging = true;
+                            mChallengeView.setLayerType(LAYER_TYPE_HARDWARE, null);
                             break;
                         }
                     }
@@ -631,6 +685,52 @@
     }
 
     /**
+     * The lifecycle of touch events is subtle and it's very easy to do something
+     * that will cause bugs that will be nasty to track when overriding this method.
+     * Normally one should always override onInterceptTouchEvent instead.
+     *
+     * To put it another way, don't try this at home.
+     */
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        final int action = ev.getActionMasked();
+        boolean handled = false;
+        if (action == MotionEvent.ACTION_DOWN) {
+            // Defensive programming: if we didn't get the UP or CANCEL, reset anyway.
+            mEdgeCaptured = false;
+        }
+        if (mWidgetsView != null && !mIsBouncing && (mEdgeCaptured || isEdgeSwipeBeginEvent(ev))) {
+            // Normally we would need to do a lot of extra stuff here.
+            // We can only get away with this because we haven't padded in
+            // the widget pager or otherwise transformed it during layout.
+            // We also don't support things like splitting MotionEvents.
+
+            // We set handled to captured even if dispatch is returning false here so that
+            // we don't send a different view a busted or incomplete event stream.
+            handled = mEdgeCaptured |= mWidgetsView.dispatchTouchEvent(ev);
+        }
+
+        if (!handled && !mEdgeCaptured) {
+            handled = super.dispatchTouchEvent(ev);
+        }
+
+        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
+            mEdgeCaptured = false;
+        }
+
+        return handled;
+    }
+
+    private boolean isEdgeSwipeBeginEvent(MotionEvent ev) {
+        if (ev.getActionMasked() != MotionEvent.ACTION_DOWN) {
+            return false;
+        }
+
+        final float x = ev.getX();
+        return x < mDragHandleEdgeSlop || x >= getWidth() - mDragHandleEdgeSlop;
+    }
+
+    /**
      * We only want to add additional vertical space to the drag handle when the panel is fully
      * closed.
      */
@@ -699,8 +799,16 @@
                 }
                 // We're going to play silly games with the frame's background drawable later.
                 mFrameDrawable = mChallengeView.getBackground();
+
+                if (!mHasLayout) {
+                    // Set up the margin correctly based on our content for the first run.
+                    mHasGlowpad = child.findViewById(R.id.keyguard_selector_view) != null;
+                    lp.leftMargin = lp.rightMargin = getChallengeMargin(true);
+                }
             } else if (lp.childType == LayoutParams.CHILD_TYPE_SCRIM) {
                 setScrimView(child);
+            } else if (lp.childType == LayoutParams.CHILD_TYPE_WIDGETS) {
+                mWidgetsView = child;
             }
 
             if (child.getVisibility() == GONE) continue;
@@ -980,6 +1088,7 @@
         public static final int CHILD_TYPE_NONE = 0;
         public static final int CHILD_TYPE_CHALLENGE = 2;
         public static final int CHILD_TYPE_SCRIM = 4;
+        public static final int CHILD_TYPE_WIDGETS = 5;
 
         public LayoutParams() {
             this(MATCH_PARENT, WRAP_CONTENT);