Merge "Moving focus on text should not create an Editor"
diff --git a/api/current.txt b/api/current.txt
index c3c5b24..4cdd30b 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3712,6 +3712,7 @@
     method public android.app.Notification.Builder setDefaults(int);
     method public android.app.Notification.Builder setDeleteIntent(android.app.PendingIntent);
     method public android.app.Notification.Builder setFullScreenIntent(android.app.PendingIntent, boolean);
+    method public android.app.Notification.Builder setIntruderActionsShowText(boolean);
     method public android.app.Notification.Builder setLargeIcon(android.graphics.Bitmap);
     method public android.app.Notification.Builder setLights(int, int, int);
     method public android.app.Notification.Builder setNumber(int);
@@ -3725,6 +3726,7 @@
     method public android.app.Notification.Builder setSound(android.net.Uri, int);
     method public android.app.Notification.Builder setTicker(java.lang.CharSequence);
     method public android.app.Notification.Builder setTicker(java.lang.CharSequence, android.widget.RemoteViews);
+    method public android.app.Notification.Builder setUsesIntruderAlert(boolean);
     method public android.app.Notification.Builder setVibrate(long[]);
     method public android.app.Notification.Builder setWhen(long);
   }
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 04ab407..bbb6a4e 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -918,6 +918,8 @@
         private Bundle mExtras;
         private int mPriority;
         private ArrayList<Action> mActions = new ArrayList<Action>(3);
+        private boolean mCanHasIntruder;
+        private boolean mIntruderActionsShowText;
 
         /**
          * Constructs a new Builder with the defaults:
@@ -1313,6 +1315,38 @@
             return this;
         }
 
+        /**
+         * Specify whether this notification should pop up as an
+         * "intruder alert" (a small window that shares the screen with the
+         * current activity). This sort of notification is (as the name implies)
+         * very intrusive, so use it sparingly for notifications that require
+         * the user's attention.
+         *
+         * Notes:
+         * <ul>
+         * <li>Intruder alerts only show when the screen is on.</li>
+         * <li>Intruder alerts take precedence over fullScreenIntents.</li>
+         * </ul>
+         *
+         * @param intrude Whether to pop up an intruder alert (default false).
+         */
+        public Builder setUsesIntruderAlert(boolean intrude) {
+            mCanHasIntruder = intrude;
+            return this;
+        }
+
+        /**
+         * Control text on intruder alert action buttons. By default, action
+         * buttons in intruders do not show textual labels.
+         * 
+         * @param showActionText Whether to show text labels beneath action
+         *            icons (default false).
+         */
+        public Builder setIntruderActionsShowText(boolean showActionText) {
+            mIntruderActionsShowText = showActionText;
+            return this;
+        }
+
         private void setFlag(int mask, boolean value) {
             if (value) {
                 mFlags |= mask;
@@ -1394,7 +1428,7 @@
             }
         }
 
-        private RemoteViews makeIntruderView() {
+        private RemoteViews makeIntruderView(boolean showLabels) {
             RemoteViews intruderView = new RemoteViews(mContext.getPackageName(),
                     R.layout.notification_intruder_content);
             if (mLargeIcon != null) {
@@ -1422,7 +1456,8 @@
                     final int buttonId = BUTTONS[i];
 
                     intruderView.setViewVisibility(buttonId, View.VISIBLE);
-                    intruderView.setImageViewResource(buttonId, action.icon);
+                    intruderView.setTextViewText(buttonId, showLabels ? action.title : null);
+                    intruderView.setTextViewCompoundDrawables(buttonId, 0, action.icon, 0, 0);
                     intruderView.setContentDescription(buttonId, action.title);
                     intruderView.setOnClickPendingIntent(buttonId, action.actionIntent);
                 }
@@ -1457,7 +1492,9 @@
             n.ledOffMS = mLedOffMs;
             n.defaults = mDefaults;
             n.flags = mFlags;
-            n.intruderView = makeIntruderView();
+            if (mCanHasIntruder) {
+                n.intruderView = makeIntruderView(mIntruderActionsShowText);
+            }
             if (mLedOnMs != 0 && mLedOffMs != 0) {
                 n.flags |= FLAG_SHOW_LIGHTS;
             }
diff --git a/core/java/android/database/sqlite/SQLiteSession.java b/core/java/android/database/sqlite/SQLiteSession.java
index 43efb03..9410243 100644
--- a/core/java/android/database/sqlite/SQLiteSession.java
+++ b/core/java/android/database/sqlite/SQLiteSession.java
@@ -398,16 +398,16 @@
         throwIfNoTransaction();
         assert mConnection != null;
 
-        endTransactionUnchecked(cancellationSignal);
+        endTransactionUnchecked(cancellationSignal, false);
     }
 
-    private void endTransactionUnchecked(CancellationSignal cancellationSignal) {
+    private void endTransactionUnchecked(CancellationSignal cancellationSignal, boolean yielding) {
         if (cancellationSignal != null) {
             cancellationSignal.throwIfCanceled();
         }
 
         final Transaction top = mTransactionStack;
-        boolean successful = top.mMarkedSuccessful && !top.mChildFailed;
+        boolean successful = (top.mMarkedSuccessful || yielding) && !top.mChildFailed;
 
         RuntimeException listenerException = null;
         final SQLiteTransactionListener listener = top.mListener;
@@ -534,7 +534,7 @@
         final int transactionMode = mTransactionStack.mMode;
         final SQLiteTransactionListener listener = mTransactionStack.mListener;
         final int connectionFlags = mConnectionFlags;
-        endTransactionUnchecked(cancellationSignal); // might throw
+        endTransactionUnchecked(cancellationSignal, true); // might throw
 
         if (sleepAfterYieldDelayMillis > 0) {
             try {
diff --git a/core/java/android/webkit/BrowserFrame.java b/core/java/android/webkit/BrowserFrame.java
index 72af251..7b6b54c 100644
--- a/core/java/android/webkit/BrowserFrame.java
+++ b/core/java/android/webkit/BrowserFrame.java
@@ -692,13 +692,10 @@
      * @return An InputStream to the android resource
      */
     private InputStream inputStreamForAndroidResource(String url) {
-        // This list needs to be kept in sync with the list in
-        // external/webkit/WebKit/android/WebCoreSupport/WebUrlLoaderClient.cpp
-        final String ANDROID_ASSET = "file:///android_asset/";
-        final String ANDROID_RESOURCE = "file:///android_res/";
-        final String ANDROID_CONTENT = "content:";
+        final String ANDROID_ASSET = URLUtil.ASSET_BASE;
+        final String ANDROID_RESOURCE = URLUtil.RESOURCE_BASE;
+        final String ANDROID_CONTENT = URLUtil.CONTENT_BASE;
 
-        // file:///android_res
         if (url.startsWith(ANDROID_RESOURCE)) {
             url = url.replaceFirst(ANDROID_RESOURCE, "");
             if (url == null || url.length() == 0) {
@@ -736,8 +733,6 @@
                 Log.e(LOGTAG, "Exception: " + url);
                 return null;
             }
-
-        // file:///android_asset
         } else if (url.startsWith(ANDROID_ASSET)) {
             url = url.replaceFirst(ANDROID_ASSET, "");
             try {
@@ -747,8 +742,6 @@
             } catch (IOException e) {
                 return null;
             }
-
-        // content://
         } else if (mSettings.getAllowContentAccess() &&
                    url.startsWith(ANDROID_CONTENT)) {
             try {
diff --git a/core/java/android/webkit/CacheManager.java b/core/java/android/webkit/CacheManager.java
index 6a85e00..671c064 100644
--- a/core/java/android/webkit/CacheManager.java
+++ b/core/java/android/webkit/CacheManager.java
@@ -17,25 +17,18 @@
 package android.webkit;
 
 import android.content.Context;
-import android.net.http.AndroidHttpClient;
 import android.net.http.Headers;
-import android.os.FileUtils;
 import android.util.Log;
+
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.FilenameFilter;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.util.List;
 import java.util.Map;
 
 
-import com.android.org.bouncycastle.crypto.Digest;
-import com.android.org.bouncycastle.crypto.digests.SHA1Digest;
-
 /**
  * Manages the HTTP cache used by an application's {@link WebView} instances.
  * @deprecated Access to the HTTP cache will be removed in a future release.
diff --git a/core/java/android/webkit/CookieManager.java b/core/java/android/webkit/CookieManager.java
index 497cab7..5f7ef41 100644
--- a/core/java/android/webkit/CookieManager.java
+++ b/core/java/android/webkit/CookieManager.java
@@ -18,21 +18,10 @@
 
 import android.net.ParseException;
 import android.net.WebAddress;
-import android.net.http.AndroidHttpClient;
 import android.os.AsyncTask;
 import android.util.Log;
 
 
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Comparator;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.SortedSet;
-import java.util.TreeSet;
-
 /**
  * Manages the cookies used by an application's {@link WebView} instances.
  * Cookies are manipulated according to RFC2109.
@@ -43,197 +32,8 @@
 
     private static final String LOGTAG = "webkit";
 
-    private static final String DOMAIN = "domain";
-
-    private static final String PATH = "path";
-
-    private static final String EXPIRES = "expires";
-
-    private static final String SECURE = "secure";
-
-    private static final String MAX_AGE = "max-age";
-
-    private static final String HTTP_ONLY = "httponly";
-
-    private static final String HTTPS = "https";
-
-    private static final char PERIOD = '.';
-
-    private static final char COMMA = ',';
-
-    private static final char SEMICOLON = ';';
-
-    private static final char EQUAL = '=';
-
-    private static final char PATH_DELIM = '/';
-
-    private static final char QUESTION_MARK = '?';
-
-    private static final char WHITE_SPACE = ' ';
-
-    private static final char QUOTATION = '\"';
-
-    private static final int SECURE_LENGTH = SECURE.length();
-
-    private static final int HTTP_ONLY_LENGTH = HTTP_ONLY.length();
-
-    // RFC2109 defines 4k as maximum size of a cookie
-    private static final int MAX_COOKIE_LENGTH = 4 * 1024;
-
-    // RFC2109 defines 20 as max cookie count per domain. As we track with base
-    // domain, we allow 50 per base domain
-    private static final int MAX_COOKIE_COUNT_PER_BASE_DOMAIN = 50;
-
-    // RFC2109 defines 300 as max count of domains. As we track with base
-    // domain, we set 200 as max base domain count
-    private static final int MAX_DOMAIN_COUNT = 200;
-
-    // max cookie count to limit RAM cookie takes less than 100k, it is based on
-    // average cookie entry size is less than 100 bytes
-    private static final int MAX_RAM_COOKIES_COUNT = 1000;
-
-    //  max domain count to limit RAM cookie takes less than 100k,
-    private static final int MAX_RAM_DOMAIN_COUNT = 15;
-
     private int mPendingCookieOperations = 0;
 
-    /**
-     * This contains a list of 2nd-level domains that aren't allowed to have
-     * wildcards when combined with country-codes. For example: [.co.uk].
-     */
-    private final static String[] BAD_COUNTRY_2LDS =
-          { "ac", "co", "com", "ed", "edu", "go", "gouv", "gov", "info",
-            "lg", "ne", "net", "or", "org" };
-
-    static {
-        Arrays.sort(BAD_COUNTRY_2LDS);
-    }
-
-    /**
-     * Package level class to be accessed by cookie sync manager
-     */
-    static class Cookie {
-        static final byte MODE_NEW = 0;
-
-        static final byte MODE_NORMAL = 1;
-
-        static final byte MODE_DELETED = 2;
-
-        static final byte MODE_REPLACED = 3;
-
-        String domain;
-
-        String path;
-
-        String name;
-
-        String value;
-
-        long expires;
-
-        long lastAcessTime;
-
-        long lastUpdateTime;
-
-        boolean secure;
-
-        byte mode;
-
-        Cookie() {
-        }
-
-        Cookie(String defaultDomain, String defaultPath) {
-            domain = defaultDomain;
-            path = defaultPath;
-            expires = -1;
-        }
-
-        boolean exactMatch(Cookie in) {
-            // An exact match means that domain, path, and name are equal. If
-            // both values are null, the cookies match. If both values are
-            // non-null, the cookies match. If one value is null and the other
-            // is non-null, the cookies do not match (i.e. "foo=;" and "foo;")
-            boolean valuesMatch = !((value == null) ^ (in.value == null));
-            return domain.equals(in.domain) && path.equals(in.path) &&
-                    name.equals(in.name) && valuesMatch;
-        }
-
-        boolean domainMatch(String urlHost) {
-            if (domain.startsWith(".")) {
-                if (urlHost.endsWith(domain.substring(1))) {
-                    int len = domain.length();
-                    int urlLen = urlHost.length();
-                    if (urlLen > len - 1) {
-                        // make sure bar.com doesn't match .ar.com
-                        return urlHost.charAt(urlLen - len) == PERIOD;
-                    }
-                    return true;
-                }
-                return false;
-            } else {
-                // exact match if domain is not leading w/ dot
-                return urlHost.equals(domain);
-            }
-        }
-
-        boolean pathMatch(String urlPath) {
-            if (urlPath.startsWith(path)) {
-                int len = path.length();
-                if (len == 0) {
-                    Log.w(LOGTAG, "Empty cookie path");
-                    return false;
-                }
-                int urlLen = urlPath.length();
-                if (path.charAt(len-1) != PATH_DELIM && urlLen > len) {
-                    // make sure /wee doesn't match /we
-                    return urlPath.charAt(len) == PATH_DELIM;
-                }
-                return true;
-            }
-            return false;
-        }
-
-        public String toString() {
-            return "domain: " + domain + "; path: " + path + "; name: " + name
-                    + "; value: " + value;
-        }
-    }
-
-    private static final CookieComparator COMPARATOR = new CookieComparator();
-
-    private static final class CookieComparator implements Comparator<Cookie> {
-        public int compare(Cookie cookie1, Cookie cookie2) {
-            // According to RFC 2109, multiple cookies are ordered in a way such
-            // that those with more specific Path attributes precede those with
-            // less specific. Ordering with respect to other attributes (e.g.,
-            // Domain) is unspecified.
-            // As Set is not modified if the two objects are same, we do want to
-            // assign different value for each cookie.
-            int diff = cookie2.path.length() - cookie1.path.length();
-            if (diff != 0) return diff;
-
-            diff = cookie2.domain.length() - cookie1.domain.length();
-            if (diff != 0) return diff;
-
-            // If cookie2 has a null value, it should come later in
-            // the list.
-            if (cookie2.value == null) {
-                // If both cookies have null values, fall back to using the name
-                // difference.
-                if (cookie1.value != null) {
-                    return -1;
-                }
-            } else if (cookie1.value == null) {
-                // Now we know that cookie2 does not have a null value, if
-                // cookie1 has a null value, place it later in the list.
-                return 1;
-            }
-
-            // Fallback to comparing the name to ensure consistent order.
-            return cookie1.name.compareTo(cookie2.name);
-        }
-    }
-
     private CookieManager() {
     }
 
diff --git a/core/java/android/webkit/CookieSyncManager.java b/core/java/android/webkit/CookieSyncManager.java
index 19fa096..4e99335 100644
--- a/core/java/android/webkit/CookieSyncManager.java
+++ b/core/java/android/webkit/CookieSyncManager.java
@@ -18,10 +18,7 @@
 
 import android.content.Context;
 import android.util.Log;
-import android.webkit.CookieManager.Cookie;
 
-import java.util.ArrayList;
-import java.util.Iterator;
 
 /**
  * The CookieSyncManager is used to synchronize the browser cookie store
@@ -62,9 +59,6 @@
 
     private static CookieSyncManager sRef;
 
-    // time when last update happened
-    private long mLastUpdate;
-
     private CookieSyncManager(Context context) {
         super(context, "CookieSyncManager");
     }
diff --git a/core/java/android/webkit/JniUtil.java b/core/java/android/webkit/JniUtil.java
index 343d34a..e3e6092 100644
--- a/core/java/android/webkit/JniUtil.java
+++ b/core/java/android/webkit/JniUtil.java
@@ -100,7 +100,7 @@
         return sContext.getPackageName();
     }
 
-    private static final String ANDROID_CONTENT = "content:";
+    private static final String ANDROID_CONTENT = URLUtil.CONTENT_BASE;
 
     /**
      * Called by JNI. Calculates the size of an input stream by reading it.
diff --git a/core/java/android/webkit/MimeTypeMap.java b/core/java/android/webkit/MimeTypeMap.java
index 35483c9..da8901a 100644
--- a/core/java/android/webkit/MimeTypeMap.java
+++ b/core/java/android/webkit/MimeTypeMap.java
@@ -17,7 +17,6 @@
 package android.webkit;
 
 import android.text.TextUtils;
-import java.util.HashMap;
 import java.util.regex.Pattern;
 import libcore.net.MimeUtils;
 
diff --git a/core/java/android/webkit/QuadF.java b/core/java/android/webkit/QuadF.java
new file mode 100644
index 0000000..e9011e3
--- /dev/null
+++ b/core/java/android/webkit/QuadF.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2012 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 android.webkit;
+
+import android.graphics.PointF;
+
+/**
+ * A quadrilateral, determined by four points, clockwise order. Typically
+ * p1 is "top-left" and p4 is "bottom-left" following webkit's rectangle-to-
+ * FloatQuad conversion.
+ */
+class QuadF {
+    public PointF p1;
+    public PointF p2;
+    public PointF p3;
+    public PointF p4;
+
+    public QuadF() {
+        p1 = new PointF();
+        p2 = new PointF();
+        p3 = new PointF();
+        p4 = new PointF();
+    }
+
+    public void offset(float dx, float dy) {
+        p1.offset(dx, dy);
+        p2.offset(dx, dy);
+        p3.offset(dx, dy);
+        p4.offset(dx, dy);
+    }
+
+    /**
+     * Determines if the quadrilateral contains the given point. This does
+     * not work if the quadrilateral is self-intersecting or if any inner
+     * angle is reflex (greater than 180 degrees).
+     */
+    public boolean containsPoint(float x, float y) {
+        return isPointInTriangle(x, y, p1, p2, p3) ||
+                isPointInTriangle(x, y, p1, p3, p4);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder s = new StringBuilder("QuadF(");
+        s.append(p1.x).append(",").append(p1.y);
+        s.append(" - ");
+        s.append(p2.x).append(",").append(p2.y);
+        s.append(" - ");
+        s.append(p3.x).append(",").append(p3.y);
+        s.append(" - ");
+        s.append(p4.x).append(",").append(p4.y);
+        s.append(")");
+        return s.toString();
+    }
+
+    private static boolean isPointInTriangle(float x0, float y0,
+            PointF r1, PointF r2, PointF r3) {
+        // Use the barycentric technique
+        float x13 = r1.x - r3.x;
+        float y13 = r1.y - r3.y;
+        float x23 = r2.x - r3.x;
+        float y23 = r2.y - r3.y;
+        float x03 = x0 - r3.x;
+        float y03 = y0 - r3.y;
+
+        float determinant = (y23 * x13) - (x23 * y13);
+        float lambda1 = ((y23 * x03) - (x23 * y03))/determinant;
+        float lambda2 = ((x13 * y03) - (y13 * x03))/determinant;
+        float lambda3 = 1 - lambda1 - lambda2;
+        return lambda1 >= 0.0f && lambda2 >= 0.0f && lambda3 >= 0.0f;
+    }
+}
diff --git a/core/java/android/webkit/URLUtil.java b/core/java/android/webkit/URLUtil.java
index 9970c93..b47f04f 100644
--- a/core/java/android/webkit/URLUtil.java
+++ b/core/java/android/webkit/URLUtil.java
@@ -38,6 +38,7 @@
     static final String RESOURCE_BASE = "file:///android_res/";
     static final String FILE_BASE = "file://";
     static final String PROXY_BASE = "file:///cookieless_proxy/";
+    static final String CONTENT_BASE = "content:";
 
     /**
      * Cleans up (if possible) user-entered web addresses
@@ -253,7 +254,7 @@
      * @return True iff the url is a content: url.
      */
     public static boolean isContentUrl(String url) {
-        return (null != url) && url.startsWith("content:");
+        return (null != url) && url.startsWith(CONTENT_BASE);
     }
 
     /**
diff --git a/core/java/android/webkit/WebViewClassic.java b/core/java/android/webkit/WebViewClassic.java
index 5ae2fe0..7ddff8e 100644
--- a/core/java/android/webkit/WebViewClassic.java
+++ b/core/java/android/webkit/WebViewClassic.java
@@ -43,6 +43,7 @@
 import android.graphics.PaintFlagsDrawFilter;
 import android.graphics.Picture;
 import android.graphics.Point;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.Region;
@@ -56,9 +57,7 @@
 import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.Looper;
 import android.os.Message;
-import android.os.StrictMode;
 import android.os.SystemClock;
 import android.provider.Settings;
 import android.security.KeyChain;
@@ -758,22 +757,21 @@
             this.setContentView(mContentView);
         }
 
-        public void show(Rect cursorRect, int windowLeft, int windowTop) {
+        public void show(Point cursorBottom, Point cursorTop,
+                int windowLeft, int windowTop) {
             measureContent();
 
             int width = mContentView.getMeasuredWidth();
             int height = mContentView.getMeasuredHeight();
-            int y = cursorRect.top - height;
+            int y = cursorTop.y - height;
+            int x = cursorTop.x - (width / 2);
             if (y < windowTop) {
                 // There's not enough room vertically, move it below the
                 // handle.
-                // The selection handle is vertically offset by 1/4 of the
-                // line height.
                 ensureSelectionHandles();
-                y = cursorRect.bottom - (cursorRect.height() / 4) +
-                        mSelectHandleCenter.getIntrinsicHeight();
+                y = cursorBottom.y + mSelectHandleCenter.getIntrinsicHeight();
+                x = cursorBottom.x - (width / 2);
             }
-            int x = cursorRect.centerX() - (width / 2);
             if (x < windowLeft) {
                 x = windowLeft;
             }
@@ -1151,12 +1149,18 @@
     private Drawable mSelectHandleLeft;
     private Drawable mSelectHandleRight;
     private Drawable mSelectHandleCenter;
-    private Rect mSelectCursorBase = new Rect();
+    private Point mSelectHandleLeftOffset;
+    private Point mSelectHandleRightOffset;
+    private Point mSelectHandleCenterOffset;
+    private Point mSelectCursorBase = new Point();
     private int mSelectCursorBaseLayerId;
-    private Rect mSelectCursorExtent = new Rect();
+    private QuadF mSelectCursorBaseTextQuad = new QuadF();
+    private Point mSelectCursorExtent = new Point();
     private int mSelectCursorExtentLayerId;
-    private Rect mSelectDraggingCursor;
-    private Point mSelectDraggingOffset = new Point();
+    private QuadF mSelectCursorExtentTextQuad = new QuadF();
+    private Point mSelectDraggingCursor;
+    private Point mSelectDraggingOffset;
+    private QuadF mSelectDraggingTextQuad;
     private boolean mIsCaretSelection;
     static final int HANDLE_ID_START = 0;
     static final int HANDLE_ID_END = 1;
@@ -3894,9 +3898,11 @@
         if (mSelectingText) {
             if (mSelectCursorBaseLayerId == mCurrentScrollingLayerId) {
                 mSelectCursorBase.offset(dx, dy);
+                mSelectCursorBaseTextQuad.offset(dx, dy);
             }
             if (mSelectCursorExtentLayerId == mCurrentScrollingLayerId) {
                 mSelectCursorExtent.offset(dx, dy);
+                mSelectCursorExtentTextQuad.offset(dx, dy);
             }
         }
         if (mAutoCompletePopup != null &&
@@ -4774,6 +4780,13 @@
                     com.android.internal.R.drawable.text_select_handle_left);
             mSelectHandleRight = mContext.getResources().getDrawable(
                     com.android.internal.R.drawable.text_select_handle_right);
+            mSelectHandleCenterOffset = new Point(0,
+                    -mSelectHandleCenter.getIntrinsicHeight());
+            mSelectHandleLeftOffset = new Point(0,
+                    -mSelectHandleLeft.getIntrinsicHeight());
+            mSelectHandleRightOffset = new Point(
+                    -mSelectHandleLeft.getIntrinsicWidth() / 2,
+                    -mSelectHandleRight.getIntrinsicHeight());
         }
     }
 
@@ -4813,10 +4826,10 @@
      * startX, startY, endX, endY
      */
     private void getSelectionHandles(int[] handles) {
-        handles[0] = mSelectCursorBase.left;
-        handles[1] = mSelectCursorBase.bottom;
-        handles[2] = mSelectCursorExtent.left;
-        handles[3] = mSelectCursorExtent.bottom;
+        handles[0] = mSelectCursorBase.x;
+        handles[1] = mSelectCursorBase.y;
+        handles[2] = mSelectCursorExtent.x;
+        handles[3] = mSelectCursorExtent.y;
         if (!nativeIsBaseFirst(mNativeClass)) {
             int swap = handles[0];
             handles[0] = handles[2];
@@ -5332,17 +5345,66 @@
         ClipboardManager cm = (ClipboardManager)(mContext
                 .getSystemService(Context.CLIPBOARD_SERVICE));
         if (cm.hasPrimaryClip()) {
-            Rect cursorRect = contentToViewRect(mSelectCursorBase);
+            Point cursorPoint = new Point(contentToViewX(mSelectCursorBase.x),
+                    contentToViewY(mSelectCursorBase.y));
+            Point cursorTop = calculateCaretTop();
+            cursorTop.set(contentToViewX(cursorTop.x),
+                    contentToViewY(cursorTop.y));
+
             int[] location = new int[2];
             mWebView.getLocationInWindow(location);
-            cursorRect.offset(location[0] - getScrollX(), location[1] - getScrollY());
+            int offsetX = location[0] - getScrollX();
+            int offsetY = location[1] - getScrollY();
+            cursorPoint.offset(offsetX, offsetY);
+            cursorTop.offset(offsetX, offsetY);
             if (mPasteWindow == null) {
                 mPasteWindow = new PastePopupWindow();
             }
-            mPasteWindow.show(cursorRect, location[0], location[1]);
+            mPasteWindow.show(cursorPoint, cursorTop, location[0], location[1]);
         }
     }
 
+    /**
+     * Given segment AB, this finds the point C along AB that is closest to
+     * point and then returns it scale along AB. The scale factor is AC/AB.
+     *
+     * @param x The x coordinate of the point near segment AB that determines
+     * the scale factor.
+     * @param y The y coordinate of the point near segment AB that determines
+     * the scale factor.
+     * @param a The first point of the line segment.
+     * @param b The second point of the line segment.
+     * @return The scale factor AC/AB, where C is the point on AB closest to
+     *         point.
+     */
+    private static float scaleAlongSegment(int x, int y, PointF a, PointF b) {
+        // The bottom line of the text box is line AB
+        float abX = b.x - a.x;
+        float abY = b.y - a.y;
+        float ab2 = (abX * abX) + (abY * abY);
+
+        // The line from first point in text bounds to bottom is AP
+        float apX = x - a.x;
+        float apY = y - a.y;
+        float abDotAP = (apX * abX) + (apY * abY);
+        float scale = abDotAP / ab2;
+        return scale;
+    }
+
+    /**
+     * Assuming arbitrary shape of a quadralateral forming text bounds, this
+     * calculates the top of a caret.
+     */
+    private Point calculateCaretTop() {
+        float scale = scaleAlongSegment(mSelectCursorBase.x, mSelectCursorBase.y,
+                mSelectCursorBaseTextQuad.p4, mSelectCursorBaseTextQuad.p3);
+        int x = Math.round(scaleCoordinate(scale,
+                mSelectCursorBaseTextQuad.p1.x, mSelectCursorBaseTextQuad.p2.x));
+        int y = Math.round(scaleCoordinate(scale,
+                mSelectCursorBaseTextQuad.p1.y, mSelectCursorBaseTextQuad.p2.y));
+        return new Point(x, y);
+    }
+
     private void hidePasteButton() {
         if (mPasteWindow != null) {
             mPasteWindow.hide();
@@ -5351,9 +5413,54 @@
 
     private void syncSelectionCursors() {
         mSelectCursorBaseLayerId =
-                nativeGetHandleLayerId(mNativeClass, HANDLE_ID_BASE, mSelectCursorBase);
+                nativeGetHandleLayerId(mNativeClass, HANDLE_ID_BASE,
+                        mSelectCursorBase, mSelectCursorBaseTextQuad);
         mSelectCursorExtentLayerId =
-                nativeGetHandleLayerId(mNativeClass, HANDLE_ID_EXTENT, mSelectCursorExtent);
+                nativeGetHandleLayerId(mNativeClass, HANDLE_ID_EXTENT,
+                        mSelectCursorExtent, mSelectCursorExtentTextQuad);
+    }
+
+    private void adjustSelectionCursors() {
+        boolean wasDraggingStart = (mSelectDraggingCursor == mSelectCursorBase);
+        int oldX = mSelectDraggingCursor.x;
+        int oldY = mSelectDraggingCursor.y;
+        int oldStartX = mSelectCursorBase.x;
+        int oldStartY = mSelectCursorBase.y;
+        int oldEndX = mSelectCursorExtent.x;
+        int oldEndY = mSelectCursorExtent.y;
+
+        syncSelectionCursors();
+        boolean dragChanged = oldX != mSelectDraggingCursor.x ||
+                oldY != mSelectDraggingCursor.y;
+        if (dragChanged && !mIsCaretSelection) {
+            boolean draggingStart;
+            if (wasDraggingStart) {
+                float endStart = distanceSquared(oldEndX, oldEndY,
+                        mSelectCursorBase);
+                float endEnd = distanceSquared(oldEndX, oldEndY,
+                        mSelectCursorExtent);
+                draggingStart = endStart > endEnd;
+            } else {
+                float startStart = distanceSquared(oldStartX, oldStartY,
+                        mSelectCursorBase);
+                float startEnd = distanceSquared(oldStartX, oldStartY,
+                        mSelectCursorExtent);
+                draggingStart = startStart > startEnd;
+            }
+            mSelectDraggingCursor = (draggingStart
+                    ? mSelectCursorBase : mSelectCursorExtent);
+            mSelectDraggingTextQuad = (draggingStart
+                    ? mSelectCursorBaseTextQuad : mSelectCursorExtentTextQuad);
+            mSelectDraggingOffset = (draggingStart
+                    ? mSelectHandleLeftOffset : mSelectHandleRightOffset);
+        }
+        mSelectDraggingCursor.set(oldX, oldY);
+    }
+
+    private float distanceSquared(int x, int y, Point p) {
+        float dx = p.x - x;
+        float dy = p.y - y;
+        return (dx * dx) + (dy * dy);
     }
 
     private boolean setupWebkitSelect() {
@@ -5370,14 +5477,14 @@
     private void updateWebkitSelection() {
         int[] handles = null;
         if (mIsCaretSelection) {
-            mSelectCursorExtent.set(mSelectCursorBase);
+            mSelectCursorExtent.set(mSelectCursorBase.x, mSelectCursorBase.y);
         }
         if (mSelectingText) {
             handles = new int[4];
-            handles[0] = mSelectCursorBase.centerX();
-            handles[1] = mSelectCursorBase.centerY();
-            handles[2] = mSelectCursorExtent.centerX();
-            handles[3] = mSelectCursorExtent.centerY();
+            handles[0] = mSelectCursorBase.x;
+            handles[1] = mSelectCursorBase.y;
+            handles[2] = mSelectCursorExtent.x;
+            handles[3] = mSelectCursorExtent.y;
         } else {
             nativeSetTextSelection(mNativeClass, 0);
         }
@@ -5968,12 +6075,15 @@
                     }
                     mSelectionStarted = false;
                     if (mSelectingText) {
+                        ensureSelectionHandles();
                         int shiftedY = y - getTitleHeight() + getScrollY();
                         int shiftedX = x + getScrollX();
                         if (mSelectHandleCenter != null && mSelectHandleCenter.getBounds()
                                 .contains(shiftedX, shiftedY)) {
                             mSelectionStarted = true;
                             mSelectDraggingCursor = mSelectCursorBase;
+                            mSelectDraggingOffset = mSelectHandleCenterOffset;
+                            mSelectDraggingTextQuad = mSelectCursorBaseTextQuad;
                             mPrivateHandler.removeMessages(CLEAR_CARET_HANDLE);
                             hidePasteButton();
                         } else if (mSelectHandleLeft != null
@@ -5981,19 +6091,18 @@
                                     .contains(shiftedX, shiftedY)) {
                                 mSelectionStarted = true;
                                 mSelectDraggingCursor = mSelectCursorBase;
+                                mSelectDraggingOffset = mSelectHandleLeftOffset;
+                                mSelectDraggingTextQuad = mSelectCursorBaseTextQuad;
                         } else if (mSelectHandleRight != null
                                 && mSelectHandleRight.getBounds()
                                 .contains(shiftedX, shiftedY)) {
                             mSelectionStarted = true;
                             mSelectDraggingCursor = mSelectCursorExtent;
+                            mSelectDraggingOffset = mSelectHandleRightOffset;
+                            mSelectDraggingTextQuad = mSelectCursorExtentTextQuad;
                         } else if (mIsCaretSelection) {
                             selectionDone();
                         }
-                        if (mSelectDraggingCursor != null) {
-                            mSelectDraggingOffset.set(
-                                    mSelectDraggingCursor.left - contentX,
-                                    mSelectDraggingCursor.top - contentY);
-                        }
                         if (DebugFlags.WEB_VIEW) {
                             Log.v(LOGTAG, "select=" + contentX + "," + contentY);
                         }
@@ -6073,9 +6182,7 @@
                         parent.requestDisallowInterceptTouchEvent(true);
                     }
                     if (deltaX != 0 || deltaY != 0) {
-                        mSelectDraggingCursor.offsetTo(
-                                contentX + mSelectDraggingOffset.x,
-                                contentY + mSelectDraggingOffset.y);
+                        snapDraggingCursor(contentX, contentY);
                         updateWebkitSelection();
                         mLastTouchX = x;
                         mLastTouchY = y;
@@ -6684,6 +6791,30 @@
         mTouchMode = TOUCH_DONE_MODE;
     }
 
+    private void snapDraggingCursor(int x, int y) {
+        x += viewToContentDimension(mSelectDraggingOffset.x);
+        y += viewToContentDimension(mSelectDraggingOffset.y);
+        if (mSelectDraggingTextQuad.containsPoint(x, y)) {
+            float scale = scaleAlongSegment(x, y,
+                    mSelectDraggingTextQuad.p4, mSelectDraggingTextQuad.p3);
+            // clamp scale to ensure point is on the bottom segment
+            scale = Math.max(0.0f, scale);
+            scale = Math.min(scale, 1.0f);
+            float newX = scaleCoordinate(scale,
+                    mSelectDraggingTextQuad.p4.x, mSelectDraggingTextQuad.p3.x);
+            float newY = scaleCoordinate(scale,
+                    mSelectDraggingTextQuad.p4.y, mSelectDraggingTextQuad.p3.y);
+            mSelectDraggingCursor.set(Math.round(newX), Math.round(newY));
+        } else {
+            mSelectDraggingCursor.set(x, y);
+        }
+    }
+
+    private static float scaleCoordinate(float scale, float coord1, float coord2) {
+        float diff = coord2 - coord1;
+        return coord1 + (scale * diff);
+    }
+
     @Override
     public boolean onGenericMotionEvent(MotionEvent event) {
         if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
@@ -8735,6 +8866,8 @@
                     setupWebkitSelect();
                 } else if (!mSelectionStarted) {
                     syncSelectionCursors();
+                } else {
+                    adjustSelectionCursors();
                 }
                 if (mIsCaretSelection) {
                     resetCaretTimer();
@@ -9398,7 +9531,7 @@
     private static native void nativeSetPauseDrawing(int instance, boolean pause);
     private static native void nativeSetTextSelection(int instance, int selection);
     private static native int nativeGetHandleLayerId(int instance, int handle,
-            Rect cursorLocation);
+            Point cursorLocation, QuadF textQuad);
     private static native boolean nativeIsBaseFirst(int instance);
     private static native void nativeMapLayerRect(int instance, int layerId,
             Rect rect);
diff --git a/core/res/res/layout/notification_intruder_content.xml b/core/res/res/layout/notification_intruder_content.xml
index 61be5f5..7f37032 100644
--- a/core/res/res/layout/notification_intruder_content.xml
+++ b/core/res/res/layout/notification_intruder_content.xml
@@ -2,7 +2,6 @@
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:orientation="vertical"
-    android:background="#FF333333"
     android:padding="4dp"
     >
     <ImageView android:id="@+id/icon"
@@ -39,29 +38,35 @@
     <LinearLayout
         android:id="@+id/actions"
         android:layout_width="match_parent"
-        android:layout_height="40dp"
+        android:layout_height="wrap_content"
         android:layout_marginTop="48dp"
         android:orientation="horizontal"
         android:visibility="gone"
         >
-        <ImageView
+        <Button
+            style="?android:attr/buttonBarButtonStyle"
+            android:textAppearance="@style/TextAppearance.StatusBar.EventContent.Title"
             android:id="@+id/action0"
             android:layout_width="0dp"
-            android:layout_height="match_parent"
+            android:layout_height="wrap_content"
             android:layout_weight="1"
             android:visibility="gone"
             />
-        <ImageView
+        <Button
+            style="?android:attr/buttonBarButtonStyle"
+            android:textAppearance="@style/TextAppearance.StatusBar.EventContent.Title"
             android:id="@+id/action1"
             android:layout_width="0dp"
-            android:layout_height="match_parent"
+            android:layout_height="wrap_content"
             android:layout_weight="1"
             android:visibility="gone"
             />
-        <ImageView
+        <Button
+            style="?android:attr/buttonBarButtonStyle"
+            android:textAppearance="@style/TextAppearance.StatusBar.EventContent.Title"
             android:id="@+id/action2"
             android:layout_width="0dp"
-            android:layout_height="match_parent"
+            android:layout_height="wrap_content"
             android:layout_weight="1"
             android:visibility="gone"
             />
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/AccessPointParserHelper.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/AccessPointParserHelper.java
index 27363e8..a6057de 100644
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/AccessPointParserHelper.java
+++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/AccessPointParserHelper.java
@@ -215,7 +215,9 @@
                         config.phase2.setValue("");
                         config.ca_cert.setValue("");
                         config.client_cert.setValue("");
-                        config.private_key.setValue("");
+                        config.engine.setValue("");
+                        config.engine_id.setValue("");
+                        config.key_id.setValue("");
                         config.identity.setValue("");
                         config.anonymous_identity.setValue("");
                         break;
diff --git a/keystore/java/android/security/Credentials.java b/keystore/java/android/security/Credentials.java
index f75208d..68ba2b1 100644
--- a/keystore/java/android/security/Credentials.java
+++ b/keystore/java/android/security/Credentials.java
@@ -26,11 +26,13 @@
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStreamReader;
+import java.io.ObjectOutputStream;
 import java.io.OutputStreamWriter;
 import java.io.Reader;
 import java.io.Writer;
 import java.nio.charset.Charsets;
 import java.security.KeyPair;
+import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -73,6 +75,36 @@
     public static final String EXTENSION_PFX = ".pfx";
 
     /**
+     * Intent extra: name for the user's private key.
+     */
+    public static final String EXTRA_USER_PRIVATE_KEY_NAME = "user_private_key_name";
+
+    /**
+     * Intent extra: data for the user's private key in PEM-encoded PKCS#8.
+     */
+    public static final String EXTRA_USER_PRIVATE_KEY_DATA = "user_private_key_data";
+
+    /**
+     * Intent extra: name for the user's certificate.
+     */
+    public static final String EXTRA_USER_CERTIFICATE_NAME = "user_certificate_name";
+
+    /**
+     * Intent extra: data for the user's certificate in PEM-encoded X.509.
+     */
+    public static final String EXTRA_USER_CERTIFICATE_DATA = "user_certificate_data";
+
+    /**
+     * Intent extra: name for CA certificate chain
+     */
+    public static final String EXTRA_CA_CERTIFICATES_NAME = "ca_certificates_name";
+
+    /**
+     * Intent extra: data for CA certificate chain in PEM-encoded X.509.
+     */
+    public static final String EXTRA_CA_CERTIFICATES_DATA = "ca_certificates_data";
+
+    /**
      * Convert objects to a PEM format, which is used for
      * CA_CERTIFICATE, USER_CERTIFICATE, and USER_PRIVATE_KEY
      * entries.
diff --git a/keystore/java/android/security/IKeyChainService.aidl b/keystore/java/android/security/IKeyChainService.aidl
index f38f6ce..60fd7f7 100644
--- a/keystore/java/android/security/IKeyChainService.aidl
+++ b/keystore/java/android/security/IKeyChainService.aidl
@@ -23,7 +23,7 @@
  */
 interface IKeyChainService {
     // APIs used by KeyChain
-    byte[] getPrivateKey(String alias);
+    String requestPrivateKey(String alias);
     byte[] getCertificate(String alias);
 
     // APIs used by CertInstaller
diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java
index fe03437..483ccb2 100644
--- a/keystore/java/android/security/KeyChain.java
+++ b/keystore/java/android/security/KeyChain.java
@@ -27,6 +27,7 @@
 import java.io.ByteArrayInputStream;
 import java.io.Closeable;
 import java.io.IOException;
+import java.security.InvalidKeyException;
 import java.security.KeyPair;
 import java.security.Principal;
 import java.security.PrivateKey;
@@ -39,6 +40,8 @@
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.LinkedBlockingQueue;
 import libcore.util.Objects;
+
+import org.apache.harmony.xnet.provider.jsse.OpenSSLEngine;
 import org.apache.harmony.xnet.provider.jsse.TrustedCertificateStore;
 
 /**
@@ -301,14 +304,21 @@
         }
         KeyChainConnection keyChainConnection = bind(context);
         try {
-            IKeyChainService keyChainService = keyChainConnection.getService();
-            byte[] privateKeyBytes = keyChainService.getPrivateKey(alias);
-            return toPrivateKey(privateKeyBytes);
+            final IKeyChainService keyChainService = keyChainConnection.getService();
+            final String keyId = keyChainService.requestPrivateKey(alias);
+            if (keyId == null) {
+                throw new KeyChainException("keystore had a problem");
+            }
+
+            final OpenSSLEngine engine = OpenSSLEngine.getInstance("keystore");
+            return engine.getPrivateKeyById(keyId);
         } catch (RemoteException e) {
             throw new KeyChainException(e);
         } catch (RuntimeException e) {
             // only certain RuntimeExceptions can be propagated across the IKeyChainService call
             throw new KeyChainException(e);
+        } catch (InvalidKeyException e) {
+            throw new KeyChainException(e);
         } finally {
             keyChainConnection.close();
         }
@@ -356,18 +366,6 @@
         }
     }
 
-    private static PrivateKey toPrivateKey(byte[] bytes) {
-        if (bytes == null) {
-            throw new IllegalArgumentException("bytes == null");
-        }
-        try {
-            KeyPair keyPair = (KeyPair) Credentials.convertFromPem(bytes).get(0);
-            return keyPair.getPrivate();
-        } catch (IOException e) {
-            throw new AssertionError(e);
-        }
-    }
-
     private static X509Certificate toCertificate(byte[] bytes) {
         if (bytes == null) {
             throw new IllegalArgumentException("bytes == null");
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index 9058cae..a32e469 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -155,6 +155,78 @@
         return mError == KEY_NOT_FOUND;
     }
 
+    private boolean generate(byte[] key) {
+        execute('a', key);
+        return mError == NO_ERROR;
+    }
+
+    public boolean generate(String key) {
+        return generate(getBytes(key));
+    }
+
+    private boolean importKey(byte[] keyName, byte[] key) {
+        execute('m', keyName, key);
+        return mError == NO_ERROR;
+    }
+
+    public boolean importKey(String keyName, byte[] key) {
+        return importKey(getBytes(keyName), key);
+    }
+
+    private byte[] getPubkey(byte[] key) {
+        ArrayList<byte[]> values = execute('b', key);
+        return (values == null || values.isEmpty()) ? null : values.get(0);
+    }
+
+    public byte[] getPubkey(String key) {
+        return getPubkey(getBytes(key));
+    }
+
+    private boolean delKey(byte[] key) {
+        execute('k', key);
+        return mError == NO_ERROR;
+    }
+
+    public boolean delKey(String key) {
+        return delKey(getBytes(key));
+    }
+
+    private byte[] sign(byte[] keyName, byte[] data) {
+        final ArrayList<byte[]> values = execute('n', keyName, data);
+        return (values == null || values.isEmpty()) ? null : values.get(0);
+    }
+
+    public byte[] sign(String key, byte[] data) {
+        return sign(getBytes(key), data);
+    }
+
+    private boolean verify(byte[] keyName, byte[] data, byte[] signature) {
+        execute('v', keyName, data, signature);
+        return mError == NO_ERROR;
+    }
+
+    public boolean verify(String key, byte[] data, byte[] signature) {
+        return verify(getBytes(key), data, signature);
+    }
+
+    private boolean grant(byte[] key, byte[] uid) {
+        execute('x', key, uid);
+        return mError == NO_ERROR;
+    }
+
+    public boolean grant(String key, int uid) {
+        return grant(getBytes(key), Integer.toString(uid).getBytes());
+    }
+
+    private boolean ungrant(byte[] key, byte[] uid) {
+        execute('y', key, uid);
+        return mError == NO_ERROR;
+    }
+
+    public boolean ungrant(String key, int uid) {
+        return ungrant(getBytes(key), Integer.toString(uid).getBytes());
+    }
+
     public int getLastError() {
         return mError;
     }
diff --git a/keystore/tests/src/android/security/KeyStoreTest.java b/keystore/tests/src/android/security/KeyStoreTest.java
index 15e253c..008d682 100755
--- a/keystore/tests/src/android/security/KeyStoreTest.java
+++ b/keystore/tests/src/android/security/KeyStoreTest.java
@@ -44,19 +44,73 @@
     private static final String TEST_I18N_KEY = "\u4F60\u597D, \u4E16\u754C";
     private static final byte[] TEST_I18N_VALUE = TEST_I18N_KEY.getBytes(Charsets.UTF_8);
 
+    // Test vector data for signatures
+    private static final byte[] TEST_DATA = {
+            (byte) 0x00, (byte) 0xA0, (byte) 0xFF, (byte) 0x0A, (byte) 0x00, (byte) 0xFF,
+            (byte) 0xAA, (byte) 0x55, (byte) 0x05, (byte) 0x5A,
+    };
+
     private KeyStore mKeyStore = null;
 
     public KeyStoreTest() {
         super(Activity.class);
     }
 
+    private static final byte[] PRIVKEY_BYTES = hexToBytes(
+            "308204BE020100300D06092A864886F70D0101010500048204A8308204A4020100028201" +
+            "0100E0473E8AB8F2284FEB9E742FF9748FA118ED98633C92F52AEB7A2EBE0D3BE60329BE" +
+            "766AD10EB6A515D0D2CFD9BEA7930F0C306537899F7958CD3E85B01F8818524D312584A9" +
+            "4B251E3625B54141EDBFEE198808E1BB97FC7CB49B9EAAAF68E9C98D7D0EDC53BBC0FA00" +
+            "34356D6305FBBCC3C7001405386ABBC873CB0F3EF7425F3D33DF7B315AE036D2A0B66AFD" +
+            "47503B169BF36E3B5162515B715FDA83DEAF2C58AEB9ABFB3097C3CC9DD9DBE5EF296C17" +
+            "6139028E8A671E63056D45F40188D2C4133490845DE52C2534E9C6B2478C07BDAE928823" +
+            "B62D066C7770F9F63F3DBA247F530844747BE7AAA85D853B8BD244ACEC3DE3C89AB46453" +
+            "AB4D24C3AC6902030100010282010037784776A5F17698F5AC960DFB83A1B67564E648BD" +
+            "0597CF8AB8087186F2669C27A9ECBDD480F0197A80D07309E6C6A96F925331E57F8B4AC6" +
+            "F4D45EDA45A23269C09FC428C07A4E6EDF738A15DEC97FABD2F2BB47A14F20EA72FCFE4C" +
+            "36E01ADA77BD137CD8D4DA10BB162E94A4662971F175F985FA188F056CB97EE2816F43AB" +
+            "9D3747612486CDA8C16196C30818A995EC85D38467791267B3BF21F273710A6925862576" +
+            "841C5B6712C12D4BD20A2F3299ADB7C135DA5E9515ABDA76E7CAF2A3BE80551D073B78BF" +
+            "1162C48AD2B7F4743A0238EE4D252F7D5E7E6533CCAE64CCB39360075A2FD1E034EC3AE5" +
+            "CE9C408CCBF0E25E4114021687B3DD4754AE8102818100F541884BC3737B2922D4119EF4" +
+            "5E2DEE2CD4CBB75F45505A157AA5009F99C73A2DF0724AC46024306332EA898177634546" +
+            "5DC6DF1E0A6F140AFF3B7396E6A8994AC5DAA96873472FE37749D14EB3E075E629DBEB35" +
+            "83338A6F3649D0A2654A7A42FD9AB6BFA4AC4D481D390BB229B064BDC311CC1BE1B63189" +
+            "DA7C40CDECF2B102818100EA1A742DDB881CEDB7288C87E38D868DD7A409D15A43F445D5" +
+            "377A0B5731DDBFCA2DAF28A8E13CD5C0AFCEC3347D74A39E235A3CD9633F274DE2B94F92" +
+            "DF43833911D9E9F1CF58F27DE2E08FF45964C720D3EC2139DC7CAFC912953CDECB2F355A" +
+            "2E2C35A50FAD754CB3B23166424BA3B6E3112A2B898C38C5C15EDB238693390281805182" +
+            "8F1EC6FD996029901BAF1D7E337BA5F0AF27E984EAD895ACE62BD7DF4EE45A224089F2CC" +
+            "151AF3CD173FCE0474BCB04F386A2CDCC0E0036BA2419F54579262D47100BE931984A3EF" +
+            "A05BECF141574DC079B3A95C4A83E6C43F3214D6DF32D512DE198085E531E616B83FD7DD" +
+            "9D1F4E2607C3333D07C55D107D1D3893587102818100DB4FB50F50DE8EDB53FF34C80931" +
+            "88A0512867DA2CCA04897759E587C244010DAF8664D59E8083D16C164789301F67A9F078" +
+            "060D834A2ADBD367575B68A8A842C2B02A89B3F31FCCEC8A22FE395795C5C6C7422B4E5D" +
+            "74A1E9A8F30E7759B9FC2D639C1F15673E84E93A5EF1506F4315383C38D45CBD1B14048F" +
+            "4721DC82326102818100D8114593AF415FB612DBF1923710D54D07486205A76A3B431949" +
+            "68C0DFF1F11EF0F61A4A337D5FD3741BBC9640E447B8B6B6C47C3AC1204357D3B0C55BA9" +
+            "286BDA73F629296F5FA9146D8976357D3C751E75148696A40B74685C82CE30902D639D72" +
+            "4FF24D5E2E9407EE34EDED2E3B4DF65AA9BCFEB6DF28D07BA6903F165768");
+
+
+    private static byte[] hexToBytes(String s) {
+        int len = s.length();
+        byte[] data = new byte[len / 2];
+        for (int i = 0; i < len; i += 2) {
+            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(
+                    s.charAt(i + 1), 16));
+        }
+        return data;
+    }
+
     @Override
     protected void setUp() throws Exception {
         mKeyStore = KeyStore.getInstance();
         if (mKeyStore.state() != KeyStore.State.UNINITIALIZED) {
             mKeyStore.reset();
         }
-        assertEquals(KeyStore.State.UNINITIALIZED, mKeyStore.state());
+        assertEquals("KeyStore should be in an uninitialized state",
+                KeyStore.State.UNINITIALIZED, mKeyStore.state());
         super.setUp();
     }
 
@@ -164,4 +218,183 @@
         mKeyStore.reset();
         assertTrue(mKeyStore.isEmpty());
     }
+
+    public void testGenerate_NotInitialized_Fail() throws Exception {
+        assertFalse("Should fail when keystore is not initialized",
+                mKeyStore.generate(TEST_KEYNAME));
+    }
+
+    public void testGenerate_Locked_Fail() throws Exception {
+        mKeyStore.password(TEST_PASSWD);
+        mKeyStore.lock();
+        assertFalse("Should fail when keystore is locked", mKeyStore.generate(TEST_KEYNAME));
+    }
+
+    public void testGenerate_Success() throws Exception {
+        mKeyStore.password(TEST_PASSWD);
+
+        assertTrue("Should be able to generate key when unlocked",
+                mKeyStore.generate(TEST_KEYNAME));
+    }
+
+    public void testImport_Success() throws Exception {
+        mKeyStore.password(TEST_PASSWD);
+
+        assertTrue("Should be able to import key when unlocked",
+                mKeyStore.importKey(TEST_KEYNAME, PRIVKEY_BYTES));
+    }
+
+    public void testImport_Failure_BadEncoding() throws Exception {
+        mKeyStore.password(TEST_PASSWD);
+
+        assertFalse("Invalid DER-encoded key should not be imported",
+                mKeyStore.importKey(TEST_KEYNAME, TEST_DATA));
+    }
+
+    public void testSign_Success() throws Exception {
+        mKeyStore.password(TEST_PASSWD);
+
+        assertTrue(mKeyStore.generate(TEST_KEYNAME));
+        final byte[] signature = mKeyStore.sign(TEST_KEYNAME, TEST_DATA);
+
+        assertNotNull("Signature should not be null", signature);
+    }
+
+    public void testVerify_Success() throws Exception {
+        mKeyStore.password(TEST_PASSWD);
+
+        assertTrue(mKeyStore.generate(TEST_KEYNAME));
+        final byte[] signature = mKeyStore.sign(TEST_KEYNAME, TEST_DATA);
+
+        assertNotNull("Signature should not be null", signature);
+
+        assertTrue("Signature should verify with same data",
+                mKeyStore.verify(TEST_KEYNAME, TEST_DATA, signature));
+    }
+
+    public void testSign_NotInitialized_Failure() throws Exception {
+        assertNull("Should not be able to sign without first initializing the keystore",
+                mKeyStore.sign(TEST_KEYNAME, TEST_DATA));
+    }
+
+    public void testSign_NotGenerated_Failure() throws Exception {
+        mKeyStore.password(TEST_PASSWD);
+
+        assertNull("Should not be able to sign without first generating keys",
+                mKeyStore.sign(TEST_KEYNAME, TEST_DATA));
+    }
+
+    public void testGrant_Generated_Success() throws Exception {
+        assertTrue("Password should work for keystore",
+                mKeyStore.password(TEST_PASSWD));
+
+        assertTrue("Should be able to generate key for testcase",
+                mKeyStore.generate(TEST_KEYNAME));
+
+        assertTrue("Should be able to grant key to other user",
+                mKeyStore.grant(TEST_KEYNAME, 0));
+    }
+
+    public void testGrant_Imported_Success() throws Exception {
+        assertTrue("Password should work for keystore", mKeyStore.password(TEST_PASSWD));
+
+        assertTrue("Should be able to import key for testcase",
+                mKeyStore.importKey(TEST_KEYNAME, PRIVKEY_BYTES));
+
+        assertTrue("Should be able to grant key to other user", mKeyStore.grant(TEST_KEYNAME, 0));
+    }
+
+    public void testGrant_NoKey_Failure() throws Exception {
+        assertTrue("Should be able to unlock keystore for test",
+                mKeyStore.password(TEST_PASSWD));
+
+        assertFalse("Should not be able to grant without first initializing the keystore",
+                mKeyStore.grant(TEST_KEYNAME, 0));
+    }
+
+    public void testGrant_NotInitialized_Failure() throws Exception {
+        assertFalse("Should not be able to grant without first initializing the keystore",
+                mKeyStore.grant(TEST_KEYNAME, 0));
+    }
+
+    public void testUngrant_Generated_Success() throws Exception {
+        assertTrue("Password should work for keystore",
+                mKeyStore.password(TEST_PASSWD));
+
+        assertTrue("Should be able to generate key for testcase",
+                mKeyStore.generate(TEST_KEYNAME));
+
+        assertTrue("Should be able to grant key to other user",
+                mKeyStore.grant(TEST_KEYNAME, 0));
+
+        assertTrue("Should be able to ungrant key to other user",
+                mKeyStore.ungrant(TEST_KEYNAME, 0));
+    }
+
+    public void testUngrant_Imported_Success() throws Exception {
+        assertTrue("Password should work for keystore",
+                mKeyStore.password(TEST_PASSWD));
+
+        assertTrue("Should be able to import key for testcase",
+                mKeyStore.importKey(TEST_KEYNAME, PRIVKEY_BYTES));
+
+        assertTrue("Should be able to grant key to other user",
+                mKeyStore.grant(TEST_KEYNAME, 0));
+
+        assertTrue("Should be able to ungrant key to other user",
+                mKeyStore.ungrant(TEST_KEYNAME, 0));
+    }
+
+    public void testUngrant_NotInitialized_Failure() throws Exception {
+        assertFalse("Should fail to ungrant key when keystore not initialized",
+                mKeyStore.ungrant(TEST_KEYNAME, 0));
+    }
+
+    public void testUngrant_NoGrant_Failure() throws Exception {
+        assertTrue("Password should work for keystore",
+                mKeyStore.password(TEST_PASSWD));
+
+        assertTrue("Should be able to generate key for testcase",
+                mKeyStore.generate(TEST_KEYNAME));
+
+        assertFalse("Should not be able to revoke not existent grant",
+                mKeyStore.ungrant(TEST_KEYNAME, 0));
+    }
+
+    public void testUngrant_DoubleUngrant_Failure() throws Exception {
+        assertTrue("Password should work for keystore",
+                mKeyStore.password(TEST_PASSWD));
+
+        assertTrue("Should be able to generate key for testcase",
+                mKeyStore.generate(TEST_KEYNAME));
+
+        assertTrue("Should be able to grant key to other user",
+                mKeyStore.grant(TEST_KEYNAME, 0));
+
+        assertTrue("Should be able to ungrant key to other user",
+                mKeyStore.ungrant(TEST_KEYNAME, 0));
+
+        assertFalse("Should fail to ungrant key to other user second time",
+                mKeyStore.ungrant(TEST_KEYNAME, 0));
+    }
+
+    public void testUngrant_DoubleGrantUngrant_Failure() throws Exception {
+        assertTrue("Password should work for keystore",
+                mKeyStore.password(TEST_PASSWD));
+
+        assertTrue("Should be able to generate key for testcase",
+                mKeyStore.generate(TEST_KEYNAME));
+
+        assertTrue("Should be able to grant key to other user",
+                mKeyStore.grant(TEST_KEYNAME, 0));
+
+        assertTrue("Should be able to grant key to other user a second time",
+                mKeyStore.grant(TEST_KEYNAME, 0));
+
+        assertTrue("Should be able to ungrant key to other user",
+                mKeyStore.ungrant(TEST_KEYNAME, 0));
+
+        assertFalse("Should fail to ungrant key to other user second time",
+                mKeyStore.ungrant(TEST_KEYNAME, 0));
+    }
 }
diff --git a/packages/SystemUI/res/drawable/intruder_row_bg.xml b/packages/SystemUI/res/drawable/intruder_row_bg.xml
new file mode 100644
index 0000000..1c7c9c4
--- /dev/null
+++ b/packages/SystemUI/res/drawable/intruder_row_bg.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:exitFadeDuration="@android:integer/config_mediumAnimTime">
+    <item android:state_pressed="true"  android:drawable="@drawable/intruder_bg_pressed" />
+</selector>
diff --git a/packages/SystemUI/res/drawable/intruder_window_bg.9.png b/packages/SystemUI/res/drawable/intruder_window_bg.9.png
new file mode 100644
index 0000000..caad169
--- /dev/null
+++ b/packages/SystemUI/res/drawable/intruder_window_bg.9.png
Binary files differ
diff --git a/packages/SystemUI/res/layout/intruder_alert.xml b/packages/SystemUI/res/layout/intruder_alert.xml
index ba4a774..c4141ae 100644
--- a/packages/SystemUI/res/layout/intruder_alert.xml
+++ b/packages/SystemUI/res/layout/intruder_alert.xml
@@ -19,40 +19,22 @@
 -->
 
 <!--    android:background="@drawable/status_bar_closed_default_background" -->
-<FrameLayout
+<com.android.systemui.statusbar.policy.IntruderAlertView
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_height="32dip"
+    android:layout_height="wrap_content"
     android:layout_width="match_parent"
-    android:paddingLeft="8dip"
-    android:paddingRight="8dip"
+    android:orientation="vertical"
     >
-        
-    <LinearLayout 
-        android:id="@+id/intruder_alert_content"
+    <FrameLayout
+        android:layout_height="wrap_content"
         android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:gravity="center"
-        android:animationCache="false"
-        android:orientation="horizontal"
-        android:background="@drawable/alert_bar_background"
-        android:clickable="true"
-        android:focusable="true"
-        android:descendantFocusability="afterDescendants"
-        >
-
-        <ImageView
-            android:id="@+id/alertIcon"
-            android:layout_width="25dip"
-            android:layout_height="25dip"
-            android:layout_marginLeft="6dip"
-            android:layout_marginRight="8dip"
-            />
-        <TextView
-            android:id="@+id/alertText"
-            android:textAppearance="@style/TextAppearance.StatusBar.IntruderAlert"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:singleLine="true"
-            />
-    </LinearLayout>
-</FrameLayout>
+        android:id="@+id/contentHolder"
+        android:background="@drawable/intruder_window_bg"
+        />
+<!--    <ImageView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:src="@drawable/title_bar_shadow"
+        android:scaleType="fitXY"
+        /> -->
+</com.android.systemui.statusbar.policy.IntruderAlertView>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 7f00c38..c8e3fad 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -30,4 +30,5 @@
     <color name="notification_list_shadow_top">#80000000</color>
     <drawable name="recents_callout_line">#99ffffff</drawable>
     <drawable name="notification_item_background_legacy_color">#ffaaaaaa</drawable>
+    <drawable name="intruder_bg_pressed">#ff33B5E5</drawable>
 </resources>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 36d9316..3a06127 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -158,4 +158,8 @@
         }
         return vetoButton;
     }
+    
+    public void dismissIntruder() {
+        // pass
+    }
 }
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 9306127..85e0a8a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -56,6 +56,7 @@
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.View;
+import android.view.ViewConfiguration;
 import android.view.ViewGroup;
 import android.view.ViewGroup.LayoutParams;
 import android.view.Window;
@@ -77,6 +78,7 @@
 import com.android.internal.statusbar.StatusBarNotification;
 
 import com.android.systemui.R;
+import com.android.systemui.SwipeHelper;
 import com.android.systemui.recent.RecentTasksLoader;
 import com.android.systemui.recent.RecentsPanelView;
 import com.android.systemui.recent.TaskDescription;
@@ -86,6 +88,7 @@
 import com.android.systemui.statusbar.SignalClusterView;
 import com.android.systemui.statusbar.policy.DateView;
 import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.IntruderAlertView;
 import com.android.systemui.statusbar.policy.LocationController;
 import com.android.systemui.statusbar.policy.NetworkController;
 import com.android.systemui.statusbar.policy.NotificationRowLayout;
@@ -102,7 +105,7 @@
     public static final String ACTION_STATUSBAR_START
             = "com.android.internal.policy.statusbar.START";
 
-    private static final boolean ENABLE_INTRUDERS = false;
+    private static final boolean ENABLE_INTRUDERS = true;
 
     static final int EXPANDED_LEAVE_ALONE = -10000;
     static final int EXPANDED_FULL_OPEN = -10001;
@@ -117,7 +120,7 @@
     private static final int MSG_CLOSE_RECENTS_PANEL = 1021;
 
     // will likely move to a resource or other tunable param at some point
-    private static final int INTRUDER_ALERT_DECAY_MS = 10000;
+    private static final int INTRUDER_ALERT_DECAY_MS = 0; // disabled, was 10000;
 
     private static final boolean CLOSE_PANEL_WHEN_EMPTIED = true;
 
@@ -185,7 +188,7 @@
     DateView mDateView;
 
     // for immersive activities
-    private View mIntruderAlertView;
+    private IntruderAlertView mIntruderAlertView;
 
     // on-screen navigation buttons
     private NavigationBarView mNavigationBarView = null;
@@ -294,9 +297,9 @@
         }
         mNotificationPanel = expanded.findViewById(R.id.notification_panel);
 
-        mIntruderAlertView = View.inflate(context, R.layout.intruder_alert, null);
+        mIntruderAlertView = (IntruderAlertView) View.inflate(context, R.layout.intruder_alert, null);
         mIntruderAlertView.setVisibility(View.GONE);
-        mIntruderAlertView.setClickable(true);
+        mIntruderAlertView.setBar(this);
 
         PhoneStatusBarView sb = (PhoneStatusBarView)View.inflate(context,
                 R.layout.status_bar, null);
@@ -455,6 +458,7 @@
             toggleRecentApps();
         }
     };
+    private StatusBarNotification mCurrentlyIntrudingNotification;
 
     private void prepareNavigationBarView() {
         mNavigationBarView.reorient();
@@ -510,7 +514,7 @@
         WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
                 ViewGroup.LayoutParams.MATCH_PARENT,
                 ViewGroup.LayoutParams.WRAP_CONTENT,
-                WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL,
+                WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, // above the status bar!
                 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
                     | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
                     | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
@@ -519,7 +523,7 @@
                     | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
                 PixelFormat.TRANSLUCENT);
         lp.gravity = Gravity.TOP | Gravity.FILL_HORIZONTAL;
-        lp.y += height * 1.5; // FIXME
+        //lp.y += height * 1.5; // FIXME
         lp.setTitle("IntruderAlert");
         lp.packageName = mContext.getPackageName();
         lp.windowAnimations = R.style.Animation_StatusBar_IntruderAlert;
@@ -562,30 +566,39 @@
         } catch (RemoteException ex) {
         }
         if (ENABLE_INTRUDERS && (
-                   (notification.score >= mIntruderInImmersiveMinScore)
-                || (!immersive && (notification.score > mIntruderMinScore)))) {
+                   // TODO(dsandler): Only if the screen is on
+                notification.notification.intruderView != null)) {
+//                   notification.notification.fullScreenIntent != null
+//                || (notification.score >= mIntruderInImmersiveMinScore)
+//                || (!immersive && (notification.score > mIntruderMinScore)))) {
             Slog.d(TAG, "Presenting high-priority notification");
             // special new transient ticker mode
             // 1. Populate mIntruderAlertView
+            
+            if (notification.notification.intruderView == null) {
+                Slog.e(TAG, notification.notification.toString() + " wanted to intrude but intruderView was null");
+                return;
+            }
 
-            ImageView alertIcon = (ImageView) mIntruderAlertView.findViewById(R.id.alertIcon);
-            TextView alertText = (TextView) mIntruderAlertView.findViewById(R.id.alertText);
-            alertIcon.setImageDrawable(StatusBarIconView.getIcon(
-                alertIcon.getContext(),
-                iconView.getStatusBarIcon()));
-            alertText.setText(notification.notification.tickerText);
+            // bind the click event to the content area
+            PendingIntent contentIntent = notification.notification.contentIntent;
+            final View.OnClickListener listener = (contentIntent != null)
+                    ? new NotificationClicker(contentIntent,
+                            notification.pkg, notification.tag, notification.id)
+                    : null;
 
-            View button = mIntruderAlertView.findViewById(R.id.intruder_alert_content);
-            button.setOnClickListener(
-                new NotificationClicker(notification.notification.contentIntent,
-                    notification.pkg, notification.tag, notification.id));
+            mIntruderAlertView.applyIntruderContent(notification.notification.intruderView, listener);
 
+            mCurrentlyIntrudingNotification = notification;
+            
             // 2. Animate mIntruderAlertView in
             mHandler.sendEmptyMessage(MSG_SHOW_INTRUDER);
 
             // 3. Set alarm to age the notification off (TODO)
             mHandler.removeMessages(MSG_HIDE_INTRUDER);
-            mHandler.sendEmptyMessageDelayed(MSG_HIDE_INTRUDER, INTRUDER_ALERT_DECAY_MS);
+            if (INTRUDER_ALERT_DECAY_MS > 0) {
+                mHandler.sendEmptyMessageDelayed(MSG_HIDE_INTRUDER, INTRUDER_ALERT_DECAY_MS);
+            }
         } else if (notification.notification.fullScreenIntent != null) {
             // not immersive & a full-screen alert should be shown
             Slog.d(TAG, "Notification has fullScreenIntent; sending fullScreenIntent");
@@ -596,8 +609,10 @@
         } else {
             // usual case: status bar visible & not immersive
 
-            // show the ticker
-            tick(notification);
+            // show the ticker if there isn't an intruder too
+            if (mCurrentlyIntrudingNotification == null) {
+                tick(notification);
+            }
         }
 
         // Recalculate the position of the sliding windows and the titles.
@@ -708,11 +723,22 @@
         // Recalculate the position of the sliding windows and the titles.
         setAreThereNotifications();
         updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
+
+        // See if we need to update the intruder.
+        if (oldNotification == mCurrentlyIntrudingNotification) {
+            if (DEBUG) Slog.d(TAG, "updating the current intruder:" + notification);
+            // XXX: this is a hack for Alarms. The real implementation will need to *update* 
+            // the intruder.
+            if (notification.notification.fullScreenIntent == null) { // TODO(dsandler): consistent logic with add()
+                if (DEBUG) Slog.d(TAG, "no longer intrudes!");
+                mHandler.sendEmptyMessage(MSG_HIDE_INTRUDER);
+            }
+        }
     }
 
     public void removeNotification(IBinder key) {
-        if (SPEW) Slog.d(TAG, "removeNotification key=" + key);
         StatusBarNotification old = removeNotificationViews(key);
+        if (SPEW) Slog.d(TAG, "removeNotification key=" + key + " old=" + old);
 
         if (old != null) {
             // Cancel the ticker if it's still running
@@ -720,6 +746,10 @@
 
             // Recalculate the position of the sliding windows and the titles.
             updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
+            
+            if (old == mCurrentlyIntrudingNotification) {
+                mHandler.sendEmptyMessage(MSG_HIDE_INTRUDER);
+            }
 
             if (CLOSE_PANEL_WHEN_EMPTIED && mNotificationData.size() == 0 && !mAnimating) {
                 animateCollapse();
@@ -1080,6 +1110,7 @@
                     break;
                 case MSG_HIDE_INTRUDER:
                     setIntruderAlertVisibility(false);
+                    mCurrentlyIntrudingNotification = null;
                     break;
                 case MSG_OPEN_RECENTS_PANEL:
                     if (DEBUG) Slog.d(TAG, "opening recents panel");
@@ -1576,6 +1607,12 @@
         }
 
         public void onClick(View v) {
+            if (DEBUG) {
+                Slog.v(TAG, "NotificationClicker: intent=" + mIntent
+                        + " pkg=" + mPkg
+                        + " tag=" + mTag
+                        + " id=" + mId);
+            }
             try {
                 // The intent we are sending is for the application, which
                 // won't have permission to immediately start an activity after
@@ -2182,9 +2219,25 @@
     };
 
     private void setIntruderAlertVisibility(boolean vis) {
+        if (DEBUG) {
+            Slog.v(TAG, (vis ? "showing" : "hiding") + " intruder alert window");
+        }
         mIntruderAlertView.setVisibility(vis ? View.VISIBLE : View.GONE);
     }
 
+    public void dismissIntruder() {
+        if (mCurrentlyIntrudingNotification == null) return;
+
+        try {
+            mBarService.onNotificationClear(
+                    mCurrentlyIntrudingNotification.pkg,
+                    mCurrentlyIntrudingNotification.tag, 
+                    mCurrentlyIntrudingNotification.id);
+        } catch (android.os.RemoteException ex) {
+            // oh well
+        }
+    }
+    
     /**
      * Reload some of our resources when the configuration changes.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IntruderAlertView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IntruderAlertView.java
new file mode 100644
index 0000000..ee5c863
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IntruderAlertView.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2011 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.policy;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.LayoutTransition;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.TypedArray;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Slog;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.RemoteViews;
+
+import com.android.systemui.R;
+import com.android.systemui.SwipeHelper;
+import com.android.systemui.statusbar.BaseStatusBar;
+
+import java.util.HashMap;
+
+public class IntruderAlertView extends LinearLayout implements SwipeHelper.Callback {
+    private static final String TAG = "IntruderAlertView";
+    private static final boolean DEBUG = false;
+
+    Rect mTmpRect = new Rect();
+
+    private SwipeHelper mSwipeHelper;
+    
+    BaseStatusBar mBar;
+    private ViewGroup mContentHolder;
+    
+    private RemoteViews mIntruderRemoteViews;
+    private OnClickListener mOnClickListener;
+
+    public IntruderAlertView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public IntruderAlertView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        setOrientation(LinearLayout.VERTICAL);
+    }
+
+    @Override
+    public void onAttachedToWindow() {
+        float densityScale = getResources().getDisplayMetrics().density;
+        float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop();
+        mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale, pagingTouchSlop);
+        
+        mContentHolder = (ViewGroup) findViewById(R.id.contentHolder);
+        if (mIntruderRemoteViews != null) {
+            // whoops, we're on already!
+            applyIntruderContent(mIntruderRemoteViews, mOnClickListener);
+        }
+    }
+    
+    public void setBar(BaseStatusBar bar) {
+        mBar = bar;
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        if (DEBUG) Log.v(TAG, "onInterceptTouchEvent()");
+        return mSwipeHelper.onInterceptTouchEvent(ev) ||
+            super.onInterceptTouchEvent(ev);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        return mSwipeHelper.onTouchEvent(ev) ||
+            super.onTouchEvent(ev);
+    }
+
+    public boolean canChildBeDismissed(View v) {
+        return true;
+    }
+
+    public void onChildDismissed(View v) {
+        Slog.v(TAG, "User swiped intruder to dismiss");
+        mBar.dismissIntruder();
+    }
+
+    public void onBeginDrag(View v) {
+    }
+
+    public void onDragCancelled(View v) {
+        mContentHolder.setAlpha(1f); // sometimes this isn't quite reset
+    }
+
+    public View getChildAtPosition(MotionEvent ev) {
+        return mContentHolder;
+    }
+
+    public View getChildContentView(View v) {
+        return v;
+    }
+
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        float densityScale = getResources().getDisplayMetrics().density;
+        mSwipeHelper.setDensityScale(densityScale);
+        float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop();
+        mSwipeHelper.setPagingTouchSlop(pagingTouchSlop);
+    }
+
+    @Override
+    public void onDraw(android.graphics.Canvas c) {
+        super.onDraw(c);
+        if (DEBUG) {
+            //Slog.d(TAG, "onDraw: canvas height: " + c.getHeight() + "px; measured height: "
+            //        + getMeasuredHeight() + "px");
+            c.save();
+            c.clipRect(6, 6, c.getWidth() - 6, getMeasuredHeight() - 6,
+                    android.graphics.Region.Op.DIFFERENCE);
+            c.drawColor(0xFFcc00cc);
+            c.restore();
+        }
+    }
+
+    public void applyIntruderContent(RemoteViews intruderView, OnClickListener listener) {
+        if (DEBUG) {
+            Slog.v(TAG, "applyIntruderContent: view=" + intruderView + " listener=" + listener);
+        }
+        mIntruderRemoteViews = intruderView;
+        mOnClickListener = listener;
+        if (mContentHolder == null) { 
+            // too soon!
+            return;
+        }
+        mContentHolder.setX(0);
+        mContentHolder.setVisibility(View.VISIBLE);
+        mContentHolder.setAlpha(1f);
+        mContentHolder.removeAllViews();
+        final View content = intruderView.apply(getContext(), mContentHolder);
+        if (listener != null) {
+            content.setOnClickListener(listener);
+            
+            //content.setBackgroundResource(R.drawable.intruder_row_bg);
+            Drawable bg = getResources().getDrawable(R.drawable.intruder_row_bg);
+            if (bg == null) {
+                Log.e(TAG, String.format("Can't find background drawable id=0x%08x", R.drawable.intruder_row_bg));
+            } else {
+                content.setBackgroundDrawable(bg);
+            }
+        }
+        mContentHolder.addView(content);
+        
+    }
+}
\ No newline at end of file
diff --git a/wifi/java/android/net/wifi/WifiConfigStore.java b/wifi/java/android/net/wifi/WifiConfigStore.java
index 5dec269..a9dbd10 100644
--- a/wifi/java/android/net/wifi/WifiConfigStore.java
+++ b/wifi/java/android/net/wifi/WifiConfigStore.java
@@ -25,6 +25,7 @@
 import android.net.NetworkInfo.DetailedState;
 import android.net.ProxyProperties;
 import android.net.RouteInfo;
+import android.net.wifi.WifiConfiguration.EnterpriseField;
 import android.net.wifi.WifiConfiguration.IpAssignment;
 import android.net.wifi.WifiConfiguration.KeyMgmt;
 import android.net.wifi.WifiConfiguration.ProxySettings;
@@ -1140,7 +1141,7 @@
                 String varName = field.varName();
                 String value = field.value();
                 if (value != null) {
-                    if (field != config.eap) {
+                    if (field != config.eap && field != config.engine) {
                         value = (value.length() == 0) ? "NULL" : convertToQuotedString(value);
                     }
                     if (!mWifiNative.setNetworkVariable(
@@ -1449,10 +1450,68 @@
             value = mWifiNative.getNetworkVariable(netId,
                     field.varName());
             if (!TextUtils.isEmpty(value)) {
-                if (field != config.eap) value = removeDoubleQuotes(value);
+                if (field != config.eap && field != config.engine) {
+                    value = removeDoubleQuotes(value);
+                }
                 field.setValue(value);
             }
         }
+
+        migrateOldEapTlsIfNecessary(config, netId);
+    }
+
+    /**
+     * Migration code for old EAP-TLS configurations. This should only be used
+     * when restoring an old wpa_supplicant.conf or upgrading from a previous
+     * platform version.
+     *
+     * @param config the configuration to be migrated
+     * @param netId the wpa_supplicant's net ID
+     * @param value the old private_key value
+     */
+    private void migrateOldEapTlsIfNecessary(WifiConfiguration config, int netId) {
+        String value = mWifiNative.getNetworkVariable(netId,
+                WifiConfiguration.OLD_PRIVATE_KEY_NAME);
+        /*
+         * If the old configuration value is not present, then there is nothing
+         * to do.
+         */
+        if (TextUtils.isEmpty(value)) {
+            return;
+        } else {
+            // Also ignore it if it's empty quotes.
+            value = removeDoubleQuotes(value);
+            if (TextUtils.isEmpty(value)) {
+                return;
+            }
+        }
+
+        config.engine.setValue(WifiConfiguration.ENGINE_ENABLE);
+        config.engine_id.setValue(convertToQuotedString(WifiConfiguration.KEYSTORE_ENGINE_ID));
+
+        /*
+         * The old key started with the keystore:// URI prefix, but we don't
+         * need that anymore. Trim it off if it exists.
+         */
+        final String keyName;
+        if (value.startsWith(WifiConfiguration.KEYSTORE_URI)) {
+            keyName = new String(value.substring(WifiConfiguration.KEYSTORE_URI.length()));
+        } else {
+            keyName = value;
+        }
+        config.key_id.setValue(convertToQuotedString(keyName));
+
+        // Now tell the wpa_supplicant the new configuration values.
+        final EnterpriseField needsUpdate[] = { config.engine, config.engine_id, config.key_id };
+        for (EnterpriseField field : needsUpdate) {
+            mWifiNative.setNetworkVariable(netId, field.varName(), field.value());
+        }
+
+        // Remove old private_key string so we don't run this again.
+        mWifiNative.setNetworkVariable(netId, WifiConfiguration.OLD_PRIVATE_KEY_NAME,
+                convertToQuotedString(""));
+
+        saveConfig();
     }
 
     private String removeDoubleQuotes(String string) {
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index 85a6f27..dfc1b18 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -29,6 +29,33 @@
  */
 public class WifiConfiguration implements Parcelable {
 
+    /**
+     * In old configurations, the "private_key" field was used. However, newer
+     * configurations use the key_id field with the engine_id set to "keystore".
+     * If this field is found in the configuration, the migration code is
+     * triggered.
+     * @hide
+     */
+    public static final String OLD_PRIVATE_KEY_NAME = "private_key";
+
+    /**
+     * String representing the keystore OpenSSL ENGINE's ID.
+     * @hide
+     */
+    public static final String KEYSTORE_ENGINE_ID = "keystore";
+
+    /**
+     * String representing the keystore URI used for wpa_supplicant.
+     * @hide
+     */
+    public static final String KEYSTORE_URI = "keystore://";
+
+    /**
+     * String to set the engine value to when it should be enabled.
+     * @hide
+     */
+    public static final String ENGINE_ENABLE = "1";
+
     /** {@hide} */
     public static final String ssidVarName = "ssid";
     /** {@hide} */
@@ -82,14 +109,18 @@
     /** {@hide} */
     public EnterpriseField client_cert = new EnterpriseField("client_cert");
     /** {@hide} */
-    public EnterpriseField private_key = new EnterpriseField("private_key");
+    public EnterpriseField engine = new EnterpriseField("engine");
+    /** {@hide} */
+    public EnterpriseField engine_id = new EnterpriseField("engine_id");
+    /** {@hide} */
+    public EnterpriseField key_id = new EnterpriseField("key_id");
     /** {@hide} */
     public EnterpriseField ca_cert = new EnterpriseField("ca_cert");
 
     /** {@hide} */
     public EnterpriseField[] enterpriseFields = {
             eap, phase2, identity, anonymous_identity, password, client_cert,
-            private_key, ca_cert };
+            engine, engine_id, key_id, ca_cert };
 
     /**
      * Recognized key management schemes.