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.