summaryrefslogtreecommitdiffstats
path: root/core/java/android/webkit
diff options
context:
space:
mode:
authorThe Android Open Source Project <initial-contribution@android.com>2008-12-17 18:05:43 -0800
committerThe Android Open Source Project <initial-contribution@android.com>2008-12-17 18:05:43 -0800
commitf013e1afd1e68af5e3b868c26a653bbfb39538f8 (patch)
tree7ad6c8fd9c7b55f4b4017171dec1cb760bbd26bf /core/java/android/webkit
parente70cfafe580c6f2994c4827cd8a534aabf3eb05c (diff)
downloadframeworks_base-f013e1afd1e68af5e3b868c26a653bbfb39538f8.zip
frameworks_base-f013e1afd1e68af5e3b868c26a653bbfb39538f8.tar.gz
frameworks_base-f013e1afd1e68af5e3b868c26a653bbfb39538f8.tar.bz2
Code drop from //branches/cupcake/...@124589
Diffstat (limited to 'core/java/android/webkit')
-rw-r--r--core/java/android/webkit/BrowserFrame.java183
-rw-r--r--core/java/android/webkit/CacheManager.java70
-rw-r--r--core/java/android/webkit/CallbackProxy.java10
-rw-r--r--core/java/android/webkit/CookieManager.java118
-rw-r--r--core/java/android/webkit/DateSorter.java23
-rw-r--r--core/java/android/webkit/FileLoader.java33
-rw-r--r--core/java/android/webkit/FrameLoader.java103
-rw-r--r--core/java/android/webkit/HttpDateTime.java2
-rw-r--r--core/java/android/webkit/JWebCoreJavaBridge.java1
-rw-r--r--core/java/android/webkit/LoadListener.java85
-rw-r--r--core/java/android/webkit/MimeTypeMap.java2
-rw-r--r--core/java/android/webkit/Network.java7
-rw-r--r--core/java/android/webkit/TextDialog.java6
-rw-r--r--core/java/android/webkit/URLUtil.java1
-rw-r--r--core/java/android/webkit/WebBackForwardList.java2
-rw-r--r--core/java/android/webkit/WebHistoryItem.java18
-rw-r--r--core/java/android/webkit/WebSettings.java308
-rw-r--r--core/java/android/webkit/WebView.java1074
-rw-r--r--core/java/android/webkit/WebViewCore.java400
-rw-r--r--core/java/android/webkit/WebViewDatabase.java76
-rw-r--r--core/java/android/webkit/gears/AndroidWifiDataProvider.java136
-rw-r--r--core/java/android/webkit/gears/DesktopAndroid.java6
-rw-r--r--core/java/android/webkit/gears/HttpRequestAndroid.java29
-rw-r--r--core/java/android/webkit/gears/NativeDialog.java142
-rw-r--r--core/java/android/webkit/gears/PluginSettings.java79
-rw-r--r--core/java/android/webkit/gears/UrlInterceptHandlerGears.java10
26 files changed, 1979 insertions, 945 deletions
diff --git a/core/java/android/webkit/BrowserFrame.java b/core/java/android/webkit/BrowserFrame.java
index e99c444..1dd37be 100644
--- a/core/java/android/webkit/BrowserFrame.java
+++ b/core/java/android/webkit/BrowserFrame.java
@@ -26,10 +26,10 @@ import android.os.Handler;
import android.os.Message;
import android.util.Config;
import android.util.Log;
+import android.util.TypedValue;
import junit.framework.Assert;
-import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Iterator;
@@ -52,9 +52,7 @@ class BrowserFrame extends Handler {
private final WebViewDatabase mDatabase;
private final WebViewCore mWebViewCore;
private boolean mLoadInitFromJava;
- private String mCurrentUrl;
private int mLoadType;
- private String mCompletedUrl;
private boolean mFirstLayoutDone = true;
private boolean mCommitted = true;
@@ -114,9 +112,7 @@ class BrowserFrame extends Handler {
CookieSyncManager.createInstance(context);
}
AssetManager am = context.getAssets();
- nativeCreateFrame(am, proxy.getBackForwardList());
- // Create a native FrameView and attach it to the native frame.
- nativeCreateView(w);
+ nativeCreateFrame(w, am, proxy.getBackForwardList());
mSettings = settings;
mContext = context;
@@ -142,11 +138,7 @@ class BrowserFrame extends Handler {
stringByEvaluatingJavaScriptFromString(
url.substring("javascript:".length()));
} else {
- if (!nativeLoadUrl(url)) {
- reportError(android.net.http.EventHandler.ERROR_BAD_URL,
- mContext.getString(com.android.internal.R.string.httpErrorBadUrl),
- url);
- }
+ nativeLoadUrl(url);
}
mLoadInitFromJava = false;
}
@@ -186,6 +178,17 @@ class BrowserFrame extends Handler {
}
/**
+ * Go back or forward the number of steps given.
+ * @param steps A negative or positive number indicating the direction
+ * and number of steps to move.
+ */
+ public void goBackOrForward(int steps) {
+ mLoadInitFromJava = true;
+ nativeGoBackOrForward(steps);
+ mLoadInitFromJava = false;
+ }
+
+ /**
* native callback
* Report an error to an activity.
* @param errorCode The HTTP error code.
@@ -216,34 +219,12 @@ class BrowserFrame extends Handler {
return mLoadType;
}
- /* package */String currentUrl() {
- return mCurrentUrl;
- }
-
- /* package */void didFirstLayout(String url) {
- // this is common case
- if (url.equals(mCurrentUrl)) {
- if (!mFirstLayoutDone) {
- mFirstLayoutDone = true;
- // ensure {@link WebViewCore#webkitDraw} is called as we were
- // blocking the update in {@link #loadStarted}
- mWebViewCore.contentInvalidate();
- }
- } else if (url.equals(mCompletedUrl)) {
- /*
- * FIXME: when loading http://www.google.com/m,
- * mCurrentUrl will be http://www.google.com/m,
- * mCompletedUrl will be http://www.google.com/m#search
- * and url will be http://www.google.com/m#search.
- * This is probably a bug in WebKit. If url matches mCompletedUrl,
- * also set mFirstLayoutDone to be true and update.
- */
- if (!mFirstLayoutDone) {
- mFirstLayoutDone = true;
- // ensure {@link WebViewCore#webkitDraw} is called as we were
- // blocking the update in {@link #loadStarted}
- mWebViewCore.contentInvalidate();
- }
+ /* package */void didFirstLayout() {
+ if (!mFirstLayoutDone) {
+ mFirstLayoutDone = true;
+ // ensure {@link WebViewCore#webkitDraw} is called as we were
+ // blocking the update in {@link #loadStarted}
+ mWebViewCore.contentDraw();
}
mWebViewCore.mEndScaleZoom = true;
}
@@ -258,8 +239,6 @@ class BrowserFrame extends Handler {
mIsMainFrame = isMainFrame;
if (isMainFrame || loadType == FRAME_LOADTYPE_STANDARD) {
- mCurrentUrl = url;
- mCompletedUrl = null;
mLoadType = loadType;
if (isMainFrame) {
@@ -310,7 +289,6 @@ class BrowserFrame extends Handler {
// mIsMainFrame and isMainFrame are better be equal!!!
if (isMainFrame || loadType == FRAME_LOADTYPE_STANDARD) {
- mCompletedUrl = url;
if (isMainFrame) {
mCallbackProxy.switchOutDrawHistory();
mCallbackProxy.onPageFinished(url);
@@ -355,8 +333,8 @@ class BrowserFrame extends Handler {
WebAddress uri = new WebAddress(
mCallbackProxy.getBackForwardList().getCurrentItem()
.getUrl());
- String host = uri.mHost;
- String[] up = mDatabase.getUsernamePassword(host);
+ String schemePlusHost = uri.mScheme + uri.mHost;
+ String[] up = mDatabase.getUsernamePassword(schemePlusHost);
if (up != null && up[0] != null) {
setUsernamePassword(up[0], up[1]);
}
@@ -441,7 +419,13 @@ class BrowserFrame extends Handler {
if (mLoadInitFromJava == true) {
return false;
}
- return mCallbackProxy.shouldOverrideUrlLoading(url);
+ if (mCallbackProxy.shouldOverrideUrlLoading(url)) {
+ // if the url is hijacked, reset the state of the BrowserFrame
+ didFirstLayout();
+ return true;
+ } else {
+ return false;
+ }
}
public void addJavascriptInterface(Object obj, String interfaceName) {
@@ -462,7 +446,7 @@ class BrowserFrame extends Handler {
* @param method The http method.
* @param headers The http headers.
* @param postData If the method is "POST" postData is sent as the request
- * body.
+ * body. Is null when empty.
* @param cacheMode The cache mode to use when loading this resource.
* @param isHighPriority True if this resource needs to be put at the front
* of the network queue.
@@ -473,7 +457,7 @@ class BrowserFrame extends Handler {
String url,
String method,
HashMap headers,
- String postData,
+ byte[] postData,
int cacheMode,
boolean isHighPriority,
boolean synchronous) {
@@ -497,41 +481,47 @@ class BrowserFrame extends Handler {
}
WebAddress uri = new WebAddress(mCallbackProxy
.getBackForwardList().getCurrentItem().getUrl());
- String host = uri.mHost;
+ String schemePlusHost = uri.mScheme + uri.mHost;
String[] ret = getUsernamePassword();
- if (ret != null && postData != null && ret[0].length() > 0
- && ret[1].length() > 0
- && postData.contains(URLEncoder.encode(ret[0]))
- && postData.contains(URLEncoder.encode(ret[1]))) {
- String[] saved = mDatabase.getUsernamePassword(host);
- if (saved != null) {
- // null username implies that user has chosen not to
- // save password
- if (saved[0] != null) {
- // non-null username implies that user has
- // chosen to save password, so update the
- // recorded password
- mDatabase.setUsernamePassword(host, ret[0],
- ret[1]);
+ // Has the user entered a username/password pair and is
+ // there some POST data
+ if (ret != null && postData != null &&
+ ret[0].length() > 0 && ret[1].length() > 0) {
+ // Check to see if the username & password appear in
+ // the post data (there could be another form on the
+ // page and that was posted instead.
+ String postString = new String(postData);
+ if (postString.contains(URLEncoder.encode(ret[0])) &&
+ postString.contains(URLEncoder.encode(ret[1]))) {
+ String[] saved = mDatabase.getUsernamePassword(
+ schemePlusHost);
+ if (saved != null) {
+ // null username implies that user has chosen not to
+ // save password
+ if (saved[0] != null) {
+ // non-null username implies that user has
+ // chosen to save password, so update the
+ // recorded password
+ mDatabase.setUsernamePassword(
+ schemePlusHost, ret[0], ret[1]);
+ }
+ } else {
+ // CallbackProxy will handle creating the resume
+ // message
+ mCallbackProxy.onSavePassword(schemePlusHost, ret[0],
+ ret[1], null);
}
- } else {
- // CallbackProxy will handle creating the resume
- // message
- mCallbackProxy.onSavePassword(host, ret[0], ret[1],
- null);
}
}
} catch (ParseException ex) {
// if it is bad uri, don't save its password
}
- }
- if (postData == null) {
- postData = "";
+
}
}
// is this resource the main-frame top-level page?
- boolean isMainFramePage = mIsMainFrame && url.equals(mCurrentUrl);
+ boolean isMainFramePage = mIsMainFrame;
if (Config.LOGV) {
Log.v(LOGTAG, "startLoadingResource: url=" + url + ", method="
@@ -561,8 +551,8 @@ class BrowserFrame extends Handler {
CacheManager.endCacheTransaction();
}
- FrameLoader loader = new FrameLoader(loadListener,
- mSettings.getUserAgentString(), method, isHighPriority);
+ FrameLoader loader = new FrameLoader(loadListener, mSettings,
+ method, isHighPriority);
loader.setHeaders(headers);
loader.setPostData(postData);
loader.setCacheMode(cacheMode); // Set the load mode to the mode used
@@ -666,36 +656,51 @@ class BrowserFrame extends Handler {
return mSettings.getUserAgentString();
}
+ // these ids need to be in sync with enum RAW_RES_ID in WebFrame
+ private static final int NODOMAIN = 1;
+ private static final int LOADERROR = 2;
+
+ String getRawResFilename(int id) {
+ int resid;
+ switch (id) {
+ case NODOMAIN:
+ resid = com.android.internal.R.raw.nodomain;
+ break;
+
+ case LOADERROR:
+ resid = com.android.internal.R.raw.loaderror;
+ break;
+
+ default:
+ Log.e(LOGTAG, "getRawResFilename got incompatible resource ID");
+ return new String();
+ }
+ TypedValue value = new TypedValue();
+ mContext.getResources().getValue(resid, value, true);
+ return value.string.toString();
+ }
+
//==========================================================================
// native functions
//==========================================================================
/**
- * Create a new native frame.
+ * Create a new native frame for a given WebView
+ * @param w A WebView that the frame draws into.
* @param am AssetManager to use to get assets.
* @param list The native side will add and remove items from this list as
* the native list changes.
*/
- private native void nativeCreateFrame(AssetManager am,
+ private native void nativeCreateFrame(WebViewCore w, AssetManager am,
WebBackForwardList list);
/**
- * Create a native view attached to a WebView.
- * @param w A WebView that the frame draws into.
- */
- private native void nativeCreateView(WebViewCore w);
-
- private native void nativeCallPolicyFunction(int policyFunction,
- int decision);
- /**
* Destroy the native frame.
*/
public native void nativeDestroyFrame();
- /**
- * Detach the view from the frame.
- */
- private native void nativeDetachView();
+ private native void nativeCallPolicyFunction(int policyFunction,
+ int decision);
/**
* Reload the current main frame.
@@ -707,7 +712,7 @@ class BrowserFrame extends Handler {
* @param steps A negative or positive number indicating the direction
* and number of steps to move.
*/
- public native void goBackOrForward(int steps);
+ private native void nativeGoBackOrForward(int steps);
/**
* stringByEvaluatingJavaScriptFromString will execute the
@@ -738,7 +743,7 @@ class BrowserFrame extends Handler {
/**
* Returns false if the url is bad.
*/
- private native boolean nativeLoadUrl(String url);
+ private native void nativeLoadUrl(String url);
private native void nativeLoadData(String baseUrl, String data,
String mimeType, String encoding, String failUrl);
diff --git a/core/java/android/webkit/CacheManager.java b/core/java/android/webkit/CacheManager.java
index f5a09b8..d12940d 100644
--- a/core/java/android/webkit/CacheManager.java
+++ b/core/java/android/webkit/CacheManager.java
@@ -62,8 +62,18 @@ public final class CacheManager {
// Reference count the enable/disable transaction
private static int mRefCount;
+ // trimCacheIfNeeded() is called when a page is fully loaded. But JavaScript
+ // can load the content, e.g. in a slideshow, continuously, so we need to
+ // trim the cache on a timer base too. endCacheTransaction() is called on a
+ // timer base. We share the same timer with less frequent update.
+ private static int mTrimCacheCount = 0;
+ private static final int TRIM_CACHE_INTERVAL = 5;
+
private static WebViewDatabase mDataBase;
private static File mBaseDir;
+
+ // Flag to clear the cache when the CacheManager is initialized
+ private static boolean mClearCacheOnInit = false;
public static class CacheResult {
// these fields are saved to the database
@@ -145,16 +155,37 @@ public final class CacheManager {
static void init(Context context) {
mDataBase = WebViewDatabase.getInstance(context);
mBaseDir = new File(context.getCacheDir(), "webviewCache");
+ if (createCacheDirectory() && mClearCacheOnInit) {
+ removeAllCacheFiles();
+ mClearCacheOnInit = false;
+ }
+ }
+
+ /**
+ * Create the cache directory if it does not already exist.
+ *
+ * @return true if the cache directory didn't exist and was created.
+ */
+ static private boolean createCacheDirectory() {
if (!mBaseDir.exists()) {
if(!mBaseDir.mkdirs()) {
Log.w(LOGTAG, "Unable to create webviewCache directory");
- return;
+ return false;
}
FileUtils.setPermissions(
mBaseDir.toString(),
FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
-1, -1);
+ // If we did create the directory, we need to flush
+ // the cache database. The directory could be recreated
+ // because the system flushed all the data/cache directories
+ // to free up disk space.
+ WebViewCore.endCacheTransaction();
+ mDataBase.clearCache();
+ WebViewCore.startCacheTransaction();
+ return true;
}
+ return false;
}
/**
@@ -224,7 +255,12 @@ public final class CacheManager {
// only called from WebCore thread
// make sure to call startCacheTransaction/endCacheTransaction in pair
public static boolean endCacheTransaction() {
- return mDataBase.endCacheTransaction();
+ boolean ret = mDataBase.endCacheTransaction();
+ if (++mTrimCacheCount >= TRIM_CACHE_INTERVAL) {
+ mTrimCacheCount = 0;
+ trimCacheIfNeeded();
+ }
+ return ret;
}
/**
@@ -319,7 +355,20 @@ public final class CacheManager {
try {
ret.outStream = new FileOutputStream(ret.outFile);
} catch (FileNotFoundException e) {
- return null;
+ // This can happen with the system did a purge and our
+ // subdirectory has gone, so lets try to create it again
+ if (createCacheDirectory()) {
+ try {
+ ret.outStream = new FileOutputStream(ret.outFile);
+ } catch (FileNotFoundException e2) {
+ // We failed to create the file again, so there
+ // is something else wrong. Return null.
+ return null;
+ }
+ } else {
+ // Failed to create cache directory
+ return null;
+ }
}
ret.mimeType = mimeType;
}
@@ -371,14 +420,25 @@ public final class CacheManager {
*/
// only called from WebCore thread
static boolean removeAllCacheFiles() {
+ // Note, this is called before init() when the database is
+ // created or upgraded.
+ if (mBaseDir == null) {
+ // Init() has not been called yet, so just flag that
+ // we need to clear the cache when init() is called.
+ mClearCacheOnInit = true;
+ return true;
+ }
// delete cache in a separate thread to not block UI.
final Runnable clearCache = new Runnable() {
public void run() {
// delete all cache files
try {
String[] files = mBaseDir.list();
- for (int i = 0; i < files.length; i++) {
- new File(mBaseDir, files[i]).delete();
+ // if mBaseDir doesn't exist, files can be null.
+ if (files != null) {
+ for (int i = 0; i < files.length; i++) {
+ new File(mBaseDir, files[i]).delete();
+ }
}
} catch (SecurityException e) {
// Ignore SecurityExceptions.
diff --git a/core/java/android/webkit/CallbackProxy.java b/core/java/android/webkit/CallbackProxy.java
index 7c296cc..cae94c9 100644
--- a/core/java/android/webkit/CallbackProxy.java
+++ b/core/java/android/webkit/CallbackProxy.java
@@ -354,12 +354,12 @@ class CallbackProxy extends Handler {
case SAVE_PASSWORD:
Bundle bundle = msg.getData();
- String host = bundle.getString("host");
+ String schemePlusHost = bundle.getString("host");
String username = bundle.getString("username");
String password = bundle.getString("password");
// If the client returned false it means that the notify message
// will not be sent and we should notify WebCore ourselves.
- if (!mWebView.onSavePassword(host, username, password,
+ if (!mWebView.onSavePassword(schemePlusHost, username, password,
(Message) msg.obj)) {
synchronized (this) {
notify();
@@ -700,8 +700,8 @@ class CallbackProxy extends Handler {
// functions just need to operate within the UI thread.
//--------------------------------------------------------------------------
- public boolean onSavePassword(String host, String username, String password,
- Message resumeMsg) {
+ public boolean onSavePassword(String schemePlusHost, String username,
+ String password, Message resumeMsg) {
// resumeMsg should be null at this point because we want to create it
// within the CallbackProxy.
if (Config.DEBUG) {
@@ -711,7 +711,7 @@ class CallbackProxy extends Handler {
Message msg = obtainMessage(SAVE_PASSWORD, resumeMsg);
Bundle bundle = msg.getData();
- bundle.putString("host", host);
+ bundle.putString("host", schemePlusHost);
bundle.putString("username", username);
bundle.putString("password", password);
synchronized (this) {
diff --git a/core/java/android/webkit/CookieManager.java b/core/java/android/webkit/CookieManager.java
index 176471f..00b17d2 100644
--- a/core/java/android/webkit/CookieManager.java
+++ b/core/java/android/webkit/CookieManager.java
@@ -151,13 +151,34 @@ public final class CookieManager {
}
boolean domainMatch(String urlHost) {
- return urlHost.equals(domain) ||
- (domain.startsWith(".") &&
- urlHost.endsWith(domain.substring(1)));
+ 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) {
- return urlPath.startsWith (path);
+ if (urlPath.startsWith(path)) {
+ int len = path.length();
+ int urlLen = urlPath.length();
+ if (urlLen > len) {
+ // make sure /wee doesn't match /we
+ return urlPath.charAt(len) == PATH_DELIM;
+ }
+ return true;
+ }
+ return false;
}
public String toString() {
@@ -232,7 +253,7 @@ public final class CookieManager {
* a system private class.
*/
public synchronized void setCookie(WebAddress uri, String value) {
- if (value != null && value.length() > 4096) {
+ if (value != null && value.length() > MAX_COOKIE_LENGTH) {
return;
}
if (!mAcceptCookie || uri == null) {
@@ -246,25 +267,19 @@ public final class CookieManager {
if (hostAndPath == null) {
return;
}
+
+ // For default path, when setting a cookie, the spec says:
+ //Path: Defaults to the path of the request URL that generated the
+ // Set-Cookie response, up to, but not including, the
+ // right-most /.
+ if (hostAndPath[1].length() > 1) {
+ int index = hostAndPath[1].lastIndexOf(PATH_DELIM);
+ hostAndPath[1] = hostAndPath[1].substring(0,
+ index > 0 ? index : index + 1);
+ }
ArrayList<Cookie> cookies = null;
try {
- /* Google is setting cookies like the following to detect whether
- * a browser supports cookie. We need to skip the leading "www" for
- * the default host. Otherwise the second cookie will make the first
- * cookie expired.
- *
- * url: https://www.google.com/accounts/ServiceLoginAuth
- * value: LSID=xxxxxxxxxxxxx;Path=/accounts;
- * Expires=Tue, 13-Mar-2018 01:41:39 GMT
- *
- * url: https://www.google.com/accounts/ServiceLoginAuth
- * value:LSID=EXPIRED;Domain=www.google.com;Path=/accounts;
- * Expires=Mon, 01-Jan-1990 00:00:00 GMT
- */
- if (hostAndPath[0].startsWith("www.")) {
- hostAndPath[0] = hostAndPath[0].substring(3);
- }
cookies = parseCookie(hostAndPath[0], hostAndPath[1], value);
} catch (RuntimeException ex) {
Log.e(LOGTAG, "parse cookie failed for: " + value);
@@ -622,26 +637,17 @@ public final class CookieManager {
/*
* find cookie path, e.g. for http://www.google.com, the path is "/"
- * for http://www.google.com/lab/, the path is "/lab/"
- * for http://www.google.com/lab/foo, the path is "/lab/"
- * for http://www.google.com/lab?hl=en, the path is "/lab/"
- * for http://www.google.com/lab.asp?hl=en, the path is "/"
+ * for http://www.google.com/lab/, the path is "/lab"
+ * for http://www.google.com/lab/foo, the path is "/lab/foo"
+ * for http://www.google.com/lab?hl=en, the path is "/lab"
+ * for http://www.google.com/lab.asp?hl=en, the path is "/lab.asp"
* Note: the path from URI has at least one "/"
+ * See:
+ * http://www.unix.com.ua/rfc/rfc2109.html
*/
index = ret[1].indexOf(QUESTION_MARK);
if (index != -1) {
ret[1] = ret[1].substring(0, index);
- if (ret[1].charAt(ret[1].length() - 1) != PATH_DELIM) {
- index = ret[1].lastIndexOf(PATH_DELIM);
- if (ret[1].lastIndexOf('.') > index) {
- ret[1] = ret[1].substring(0, index + 1);
- } else {
- ret[1] += PATH_DELIM;
- }
- }
- } else if (ret[1].charAt(ret[1].length() - 1) != PATH_DELIM) {
- ret[1] = ret[1].substring(0,
- ret[1].lastIndexOf(PATH_DELIM) + 1);
}
return ret;
} else
@@ -687,10 +693,6 @@ public final class CookieManager {
String cookieString) {
ArrayList<Cookie> ret = new ArrayList<Cookie>();
- // domain needs at least two PERIOD,
- if (host.indexOf(PERIOD) == host.lastIndexOf(PERIOD)) {
- host = PERIOD + host;
- }
int index = 0;
int length = cookieString.length();
while (true) {
@@ -841,15 +843,14 @@ public final class CookieManager {
"illegal format for max-age: " + value);
}
} else if (name.equals(PATH)) {
- // make sure path ends with PATH_DELIM
- if (value.length() > 1 &&
- value.charAt(value.length() - 1) != PATH_DELIM) {
- cookie.path = value + PATH_DELIM;
- } else {
- cookie.path = value;
- }
+ cookie.path = value;
} else if (name.equals(DOMAIN)) {
int lastPeriod = value.lastIndexOf(PERIOD);
+ if (lastPeriod == 0) {
+ // disallow cookies set for TLDs like [.com]
+ cookie.domain = null;
+ continue;
+ }
try {
Integer.parseInt(value.substring(lastPeriod + 1));
// no wildcard for ip address match
@@ -862,15 +863,22 @@ public final class CookieManager {
// ignore the exception, value is a host name
}
value = value.toLowerCase();
- if (value.endsWith(host) || host.endsWith(value)) {
- // domain needs at least two PERIOD
- if (value.indexOf(PERIOD) == lastPeriod) {
- value = PERIOD + value;
+ if (value.charAt(0) != PERIOD) {
+ // pre-pended dot to make it as a domain cookie
+ value = PERIOD + value;
+ lastPeriod++;
+ }
+ if (host.endsWith(value.substring(1))) {
+ int len = value.length();
+ int hostLen = host.length();
+ if (hostLen > (len - 1)
+ && host.charAt(hostLen - len) != PERIOD) {
+ // make sure the bar.com doesn't match .ar.com
+ cookie.domain = null;
+ continue;
}
// disallow cookies set on ccTLDs like [.co.uk]
- int len = value.length();
- if ((value.charAt(0) == PERIOD)
- && (len == lastPeriod + 3)
+ if ((len == lastPeriod + 3)
&& (len >= 6 && len <= 8)) {
String s = value.substring(1, lastPeriod);
if (Arrays.binarySearch(BAD_COUNTRY_2LDS, s) >= 0) {
@@ -880,7 +888,7 @@ public final class CookieManager {
}
cookie.domain = value;
} else {
- // no cross-site cookie
+ // no cross-site or more specific sub-domain cookie
cookie.domain = null;
}
}
diff --git a/core/java/android/webkit/DateSorter.java b/core/java/android/webkit/DateSorter.java
index 3dc15c1..750403b 100644
--- a/core/java/android/webkit/DateSorter.java
+++ b/core/java/android/webkit/DateSorter.java
@@ -17,7 +17,7 @@
package android.webkit;
import android.content.Context;
-import android.util.Log;
+import android.content.res.Resources;
import java.util.Calendar;
import java.util.Date;
@@ -40,6 +40,8 @@ public class DateSorter {
private long [] mBins = new long[DAY_COUNT];
private String [] mLabels = new String[DAY_COUNT];
+
+ private static final int NUM_DAYS_AGO = 5;
Date mDate = new Date();
Calendar mCal = Calendar.getInstance();
@@ -48,6 +50,7 @@ public class DateSorter {
* @param context Application context
*/
public DateSorter(Context context) {
+ Resources resources = context.getResources();
Calendar c = Calendar.getInstance();
beginningOfDay(c);
@@ -56,9 +59,9 @@ public class DateSorter {
mBins[0] = c.getTimeInMillis(); // Today
c.roll(Calendar.DAY_OF_YEAR, -1);
mBins[1] = c.getTimeInMillis(); // Yesterday
- c.roll(Calendar.DAY_OF_YEAR, -4);
+ c.roll(Calendar.DAY_OF_YEAR, -(NUM_DAYS_AGO - 1));
mBins[2] = c.getTimeInMillis(); // Five days ago
- c.roll(Calendar.DAY_OF_YEAR, 5); // move back to today
+ c.roll(Calendar.DAY_OF_YEAR, NUM_DAYS_AGO); // move back to today
c.roll(Calendar.MONTH, -1);
mBins[3] = c.getTimeInMillis(); // One month ago
c.roll(Calendar.MONTH, -1);
@@ -67,14 +70,14 @@ public class DateSorter {
// build labels
mLabels[0] = context.getText(com.android.internal.R.string.today).toString();
mLabels[1] = context.getText(com.android.internal.R.string.yesterday).toString();
- mLabels[2] = context.getString(com.android.internal.R.string.daysDurationPastPlural, 5);
- mLabels[3] = context.getText(com.android.internal.R.string.oneMonthDurationPast).toString();
- StringBuilder sb = new StringBuilder();
- sb.append(context.getText(com.android.internal.R.string.before)).append(" ");
- sb.append(context.getText(com.android.internal.R.string.oneMonthDurationPast));
- mLabels[4] = sb.toString();
-
+ int resId = com.android.internal.R.plurals.num_days_ago;
+ String format = resources.getQuantityString(resId, NUM_DAYS_AGO);
+ mLabels[2] = String.format(format, NUM_DAYS_AGO);
+
+ mLabels[3] = context.getText(com.android.internal.R.string.oneMonthDurationPast).toString();
+ mLabels[4] = context.getText(com.android.internal.R.string.beforeOneMonthDurationPast)
+ .toString();
}
/**
diff --git a/core/java/android/webkit/FileLoader.java b/core/java/android/webkit/FileLoader.java
index 6696bae..10343b2 100644
--- a/core/java/android/webkit/FileLoader.java
+++ b/core/java/android/webkit/FileLoader.java
@@ -16,6 +16,8 @@
package android.webkit;
+import com.android.internal.R;
+
import android.content.Context;
import android.content.res.AssetManager;
import android.net.http.EventHandler;
@@ -35,6 +37,7 @@ class FileLoader extends StreamLoader {
private String mPath; // Full path to the file to load
private Context mContext; // Application context, used for asset loads
private boolean mIsAsset; // Indicates if the load is an asset or not
+ private boolean mAllowFileAccess; // Allow/block file system access
/**
* Construct a FileLoader with the file URL specified as the content
@@ -44,12 +47,15 @@ class FileLoader extends StreamLoader {
* @param loadListener LoadListener to pass the content to
* @param context Context to use to access the asset.
* @param asset true if url points to an asset.
+ * @param allowFileAccess true if this WebView is allowed to access files
+ * on the file system.
*/
FileLoader(String url, LoadListener loadListener, Context context,
- boolean asset) {
+ boolean asset, boolean allowFileAccess) {
super(loadListener);
mIsAsset = asset;
mContext = context;
+ mAllowFileAccess = allowFileAccess;
// clean the Url
int index = url.indexOf('?');
@@ -73,35 +79,27 @@ class FileLoader extends StreamLoader {
mDataStream = mContext.getAssets().open(mPath,
AssetManager.ACCESS_STREAMING);
} else {
- mHandler.error(EventHandler.FILE_ERROR,
- mContext.getString(
- com.android.internal.R.string.httpErrorFileNotFound));
- return false;
-/*
- if (!mPath.startsWith(
- Environment.getExternalStorageDirectory().getPath())) {
+ if (!mAllowFileAccess) {
mHandler.error(EventHandler.FILE_ERROR,
- mContext.getString(
- com.android.internal.R.string.httpErrorFileNotFound));
+ mContext.getString(R.string.httpErrorFileNotFound));
return false;
}
+
mDataStream = new FileInputStream(mPath);
mContentLength = (new File(mPath)).length();
-*/
}
mHandler.status(1, 1, 0, "OK");
} catch (java.io.FileNotFoundException ex) {
mHandler.error(
EventHandler.FILE_NOT_FOUND_ERROR,
- mContext.getString(com.android.internal.R.string.httpErrorFileNotFound) +
+ mContext.getString(R.string.httpErrorFileNotFound) +
" " + ex.getMessage());
return false;
} catch (java.io.IOException ex) {
mHandler.error(EventHandler.FILE_ERROR,
- mContext.getString(
- com.android.internal.R.string.httpErrorFileNotFound) +
+ mContext.getString(R.string.httpErrorFileNotFound) +
" " + ex.getMessage());
return false;
}
@@ -121,10 +119,13 @@ class FileLoader extends StreamLoader {
* @param loadListener LoadListener to pass the content to
* @param context Context to use to access the asset.
* @param asset true if url points to an asset.
+ * @param allowFileAccess true if this FileLoader can load files from the
+ * file system.
*/
public static void requestUrl(String url, LoadListener loadListener,
- Context context, boolean asset) {
- FileLoader loader = new FileLoader(url, loadListener, context, asset);
+ Context context, boolean asset, boolean allowFileAccess) {
+ FileLoader loader = new FileLoader(url, loadListener, context, asset,
+ allowFileAccess);
loader.load();
}
diff --git a/core/java/android/webkit/FrameLoader.java b/core/java/android/webkit/FrameLoader.java
index ebfebd0..7a3bbe6 100644
--- a/core/java/android/webkit/FrameLoader.java
+++ b/core/java/android/webkit/FrameLoader.java
@@ -28,16 +28,16 @@ import java.util.Map;
class FrameLoader {
- protected LoadListener mListener;
- protected Map<String, String> mHeaders;
- protected String mMethod;
- protected String mPostData;
- protected boolean mIsHighPriority;
- protected Network mNetwork;
- protected int mCacheMode;
- protected String mReferrer;
- protected String mUserAgent;
- protected String mContentType;
+ private final LoadListener mListener;
+ private final String mMethod;
+ private final boolean mIsHighPriority;
+ private final WebSettings mSettings;
+ private Map<String, String> mHeaders;
+ private byte[] mPostData;
+ private Network mNetwork;
+ private int mCacheMode;
+ private String mReferrer;
+ private String mContentType;
private static final int URI_PROTOCOL = 0x100;
@@ -53,43 +53,14 @@ class FrameLoader {
private static final String LOGTAG = "webkit";
- /*
- * Construct the Accept_Language once. If the user changes language, then
- * the phone will be rebooted.
- */
- private static String ACCEPT_LANGUAGE;
- static {
- // Set the accept-language to the current locale plus US if we are in a
- // different locale than US.
- java.util.Locale l = java.util.Locale.getDefault();
- ACCEPT_LANGUAGE = "";
- if (l.getLanguage() != null) {
- ACCEPT_LANGUAGE += l.getLanguage();
- if (l.getCountry() != null) {
- ACCEPT_LANGUAGE += "-" + l.getCountry();
- }
- }
- if (!l.equals(java.util.Locale.US)) {
- ACCEPT_LANGUAGE += ", ";
- java.util.Locale us = java.util.Locale.US;
- if (us.getLanguage() != null) {
- ACCEPT_LANGUAGE += us.getLanguage();
- if (us.getCountry() != null) {
- ACCEPT_LANGUAGE += "-" + us.getCountry();
- }
- }
- }
- }
-
-
- FrameLoader(LoadListener listener, String userAgent,
+ FrameLoader(LoadListener listener, WebSettings settings,
String method, boolean highPriority) {
mListener = listener;
mHeaders = null;
mMethod = method;
mIsHighPriority = highPriority;
mCacheMode = WebSettings.LOAD_NORMAL;
- mUserAgent = userAgent;
+ mSettings = settings;
}
public void setReferrer(String ref) {
@@ -97,7 +68,7 @@ class FrameLoader {
if (URLUtil.isNetworkUrl(ref)) mReferrer = ref;
}
- public void setPostData(String postData) {
+ public void setPostData(byte[] postData) {
mPostData = postData;
}
@@ -140,12 +111,15 @@ class FrameLoader {
}
if (URLUtil.isNetworkUrl(url)){
+ if (mSettings.getBlockNetworkLoads()) {
+ mListener.error(EventHandler.ERROR_BAD_URL,
+ mListener.getContext().getString(
+ com.android.internal.R.string.httpErrorBadUrl));
+ return false;
+ }
mNetwork = Network.getInstance(mListener.getContext());
- return handleHTTPLoad(false);
- } else if (URLUtil.isCookielessProxyUrl(url)) {
- mNetwork = Network.getInstance(mListener.getContext());
- return handleHTTPLoad(true);
- } else if (handleLocalFile(url, mListener)) {
+ return handleHTTPLoad();
+ } else if (handleLocalFile(url, mListener, mSettings)) {
return true;
}
if (Config.LOGV) {
@@ -160,14 +134,15 @@ class FrameLoader {
}
/* package */
- static boolean handleLocalFile(String url, LoadListener loadListener) {
+ static boolean handleLocalFile(String url, LoadListener loadListener,
+ WebSettings settings) {
if (URLUtil.isAssetUrl(url)) {
FileLoader.requestUrl(url, loadListener, loadListener.getContext(),
- true);
+ true, settings.getAllowFileAccess());
return true;
} else if (URLUtil.isFileUrl(url)) {
FileLoader.requestUrl(url, loadListener, loadListener.getContext(),
- false);
+ false, settings.getAllowFileAccess());
return true;
} else if (URLUtil.isContentUrl(url)) {
// Send the raw url to the ContentLoader because it will do a
@@ -186,21 +161,12 @@ class FrameLoader {
return false;
}
- protected boolean handleHTTPLoad(boolean proxyUrl) {
+ private boolean handleHTTPLoad() {
if (mHeaders == null) {
mHeaders = new HashMap<String, String>();
}
populateStaticHeaders();
-
- if (!proxyUrl) {
- // Don't add private information if this is a proxy load, ie don't
- // add cookies and authentication
- populateHeaders();
- } else {
- // If this is a proxy URL, fix it to be a network load
- mListener.setUrl("http://"
- + mListener.url().substring(URLUtil.PROXY_BASE.length()));
- }
+ populateHeaders();
// response was handled by UrlIntercept, don't issue HTTP request
if (handleUrlIntercept()) return true;
@@ -246,7 +212,7 @@ class FrameLoader {
* This function is used by handleUrlInterecpt and handleCache to
* setup a load from the byte stream in a CacheResult.
*/
- protected void startCacheLoad(CacheResult result) {
+ private void startCacheLoad(CacheResult result) {
if (Config.LOGV) {
Log.v(LOGTAG, "FrameLoader: loading from cache: "
+ mListener.url());
@@ -264,7 +230,7 @@ class FrameLoader {
*
* Returns true if the response was handled by UrlIntercept.
*/
- protected boolean handleUrlIntercept() {
+ private boolean handleUrlIntercept() {
// Check if the URL can be served from UrlIntercept. If
// successful, return the data just like a cache hit.
CacheResult result = UrlInterceptRegistry.getSurrogate(
@@ -284,7 +250,7 @@ class FrameLoader {
* correctly.
* Returns true if the response was handled from the cache
*/
- protected boolean handleCache() {
+ private boolean handleCache() {
switch (mCacheMode) {
// This mode is normally used for a reload, it instructs the http
// loader to not use the cached content.
@@ -357,11 +323,12 @@ class FrameLoader {
}
mHeaders.put("Accept-Charset", "utf-8, iso-8859-1, utf-16, *;q=0.7");
- if (ACCEPT_LANGUAGE.length() > 0) {
- mHeaders.put("Accept-Language", ACCEPT_LANGUAGE);
+ String acceptLanguage = mSettings.getAcceptLanguage();
+ if (acceptLanguage.length() > 0) {
+ mHeaders.put("Accept-Language", acceptLanguage);
}
-
- mHeaders.put("User-Agent", mUserAgent);
+
+ mHeaders.put("User-Agent", mSettings.getUserAgentString());
}
/**
diff --git a/core/java/android/webkit/HttpDateTime.java b/core/java/android/webkit/HttpDateTime.java
index b22f2ba..c6ec2d2 100644
--- a/core/java/android/webkit/HttpDateTime.java
+++ b/core/java/android/webkit/HttpDateTime.java
@@ -16,7 +16,7 @@
package android.webkit;
-import android.pim.Time;
+import android.text.format.Time;
import java.util.Calendar;
import java.util.regex.Matcher;
diff --git a/core/java/android/webkit/JWebCoreJavaBridge.java b/core/java/android/webkit/JWebCoreJavaBridge.java
index 1cfea99..a0049ac 100644
--- a/core/java/android/webkit/JWebCoreJavaBridge.java
+++ b/core/java/android/webkit/JWebCoreJavaBridge.java
@@ -191,4 +191,5 @@ final class JWebCoreJavaBridge extends Handler {
private native void nativeFinalize();
private native void sharedTimerFired();
private native void setDeferringTimers(boolean defer);
+ public native void setNetworkOnLine(boolean online);
}
diff --git a/core/java/android/webkit/LoadListener.java b/core/java/android/webkit/LoadListener.java
index 86947a2..c45ab29 100644
--- a/core/java/android/webkit/LoadListener.java
+++ b/core/java/android/webkit/LoadListener.java
@@ -99,7 +99,7 @@ class LoadListener extends Handler implements EventHandler {
// cache. It is needed if the cache returns a redirect
private String mMethod;
private Map<String, String> mRequestHeaders;
- private String mPostData;
+ private byte[] mPostData;
private boolean mIsHighPriority;
// Flag to indicate that this load is synchronous.
private boolean mSynchronous;
@@ -220,7 +220,8 @@ class LoadListener extends Handler implements EventHandler {
*/
Message contMsg = obtainMessage(MSG_LOCATION_CHANGED);
Message stopMsg = obtainMessage(MSG_CONTENT_FINISHED);
- //TODO, need to call mCallbackProxy and request UI.
+ mBrowserFrame.getCallbackProxy().onFormResubmission(
+ stopMsg, contMsg);
break;
}
@@ -286,15 +287,16 @@ class LoadListener extends Handler implements EventHandler {
if (newMimeType != null) {
mMimeType = newMimeType;
}
- } else if (mMimeType.equalsIgnoreCase("text/vnd.wap.wml") ||
- mMimeType.
- equalsIgnoreCase("application/vnd.wap.xhtml+xml")) {
+ } else if (mMimeType.equalsIgnoreCase("text/vnd.wap.wml")) {
// As we don't support wml, render it as plain text
mMimeType = "text/plain";
} else {
// XXX: Until the servers send us either correct xhtml or
// text/html, treat application/xhtml+xml as text/html.
- if (mMimeType.equalsIgnoreCase("application/xhtml+xml")) {
+ // It seems that xhtml+xml and vnd.wap.xhtml+xml mime
+ // subtypes are used interchangeably. So treat them the same.
+ if (mMimeType.equalsIgnoreCase("application/xhtml+xml") ||
+ mMimeType.equals("application/vnd.wap.xhtml+xml")) {
mMimeType = "text/html";
}
}
@@ -527,23 +529,21 @@ class LoadListener extends Handler implements EventHandler {
} else {
sendMessageInternal(obtainMessage(MSG_LOCATION_CHANGED));
}
-
- break;
+ return;
case HTTP_AUTH:
case HTTP_PROXY_AUTH:
+ // According to rfc2616, the response for HTTP_AUTH must include
+ // WWW-Authenticate header field and the response for
+ // HTTP_PROXY_AUTH must include Proxy-Authenticate header field.
if (mAuthHeader != null &&
(Network.getInstance(mContext).isValidProxySet() ||
!mAuthHeader.isProxy())) {
Network.getInstance(mContext).handleAuthRequest(this);
- } else {
- final int stringId =
- com.android.internal.R.string.httpErrorUnsupportedAuthScheme;
- error(EventHandler.ERROR_UNSUPPORTED_AUTH_SCHEME,
- getContext().getText(stringId).toString());
+ return;
}
- break;
-
+ break; // use default
+
case HTTP_NOT_MODIFIED:
// Server could send back NOT_MODIFIED even if we didn't
// ask for it, so make sure we have a valid CacheLoader
@@ -554,16 +554,18 @@ class LoadListener extends Handler implements EventHandler {
if (Config.LOGV) {
Log.v(LOGTAG, "LoadListener cache load url=" + url());
}
- break;
- } // Fall through to default if there is no CacheLoader
+ return;
+ }
+ break; // use default
case HTTP_NOT_FOUND:
// Not an error, the server can send back content.
default:
- sendMessageInternal(obtainMessage(MSG_CONTENT_FINISHED));
- detachRequestHandle();
break;
}
+
+ sendMessageInternal(obtainMessage(MSG_CONTENT_FINISHED));
+ detachRequestHandle();
}
/**
@@ -725,7 +727,7 @@ class LoadListener extends Handler implements EventHandler {
* @param isHighPriority
*/
void setRequestData(String method, Map<String, String> headers,
- String postData, boolean isHighPriority) {
+ byte[] postData, boolean isHighPriority) {
mMethod = method;
mRequestHeaders = headers;
mPostData = postData;
@@ -878,37 +880,16 @@ class LoadListener extends Handler implements EventHandler {
int statusCode = mStatusCode == HTTP_NOT_MODIFIED
? HTTP_OK : mStatusCode;
// pass content-type content-length and content-encoding
- int nativeResponse = nativeCreateResponse(mUrl, statusCode, mStatusText,
+ final int nativeResponse = nativeCreateResponse(
+ mUrl, statusCode, mStatusText,
mMimeType, mContentLength, mEncoding,
mCacheResult == null ? 0 : mCacheResult.expires / 1000);
if (mHeaders != null) {
- // "content-disposition",
- String value = mHeaders.getContentDisposition();
- if (value != null) {
- nativeSetResponseHeader(nativeResponse,
- Headers.CONTENT_DISPOSITION, value);
- }
-
- // location
- value = mHeaders.getLocation();
- if (value != null) {
- nativeSetResponseHeader(nativeResponse,
- Headers.LOCATION, value);
- }
-
- // refresh (paypal.com are using this)
- value = mHeaders.getRefresh();
- if (value != null) {
- nativeSetResponseHeader(nativeResponse,
- Headers.REFRESH, value);
- }
-
- // Content-Type
- value = mHeaders.getContentType();
- if (value != null) {
- nativeSetResponseHeader(nativeResponse,
- Headers.CONTENT_TYPE, value);
- }
+ mHeaders.getHeaders(new Headers.HeaderCallback() {
+ public void header(String name, String value) {
+ nativeSetResponseHeader(nativeResponse, name, value);
+ }
+ });
}
return nativeResponse;
}
@@ -1048,7 +1029,6 @@ class LoadListener extends Handler implements EventHandler {
cancel();
return;
} else if (!URLUtil.isNetworkUrl(redirectTo)) {
- cancel();
final String text = mContext
.getString(com.android.internal.R.string.open_permission_deny)
+ "\n" + redirectTo;
@@ -1250,7 +1230,12 @@ class LoadListener extends Handler implements EventHandler {
*/
void setUrl(String url) {
if (url != null) {
- mUrl = URLUtil.stripAnchor(url);
+ if (URLUtil.isDataUrl(url)) {
+ // Don't strip anchor as that is a valid part of the URL
+ mUrl = url;
+ } else {
+ mUrl = URLUtil.stripAnchor(url);
+ }
mUri = null;
if (URLUtil.isNetworkUrl(mUrl)) {
try {
diff --git a/core/java/android/webkit/MimeTypeMap.java b/core/java/android/webkit/MimeTypeMap.java
index 2700aa5..85cb8c0 100644
--- a/core/java/android/webkit/MimeTypeMap.java
+++ b/core/java/android/webkit/MimeTypeMap.java
@@ -65,7 +65,7 @@ public /* package */ class MimeTypeMap {
// if the filename contains special characters, we don't
// consider it valid for our matching purposes:
if (filename.length() > 0 &&
- Pattern.matches("[a-zA-Z_0-9\\.\\-]+", filename)) {
+ Pattern.matches("[a-zA-Z_0-9\\.\\-\\(\\)]+", filename)) {
int dotPos = filename.lastIndexOf('.');
if (0 <= dotPos) {
return filename.substring(dotPos + 1);
diff --git a/core/java/android/webkit/Network.java b/core/java/android/webkit/Network.java
index ea42e58..74622b3 100644
--- a/core/java/android/webkit/Network.java
+++ b/core/java/android/webkit/Network.java
@@ -155,7 +155,7 @@ class Network {
*/
public boolean requestURL(String method,
Map<String, String> headers,
- String postData,
+ byte [] postData,
LoadListener loader,
boolean isHighPriority) {
@@ -178,9 +178,8 @@ class Network {
InputStream bodyProvider = null;
int bodyLength = 0;
if (postData != null) {
- byte[] data = postData.getBytes();
- bodyLength = data.length;
- bodyProvider = new ByteArrayInputStream(data);
+ bodyLength = postData.length;
+ bodyProvider = new ByteArrayInputStream(postData);
}
RequestQueue q = mRequestQueue;
diff --git a/core/java/android/webkit/TextDialog.java b/core/java/android/webkit/TextDialog.java
index 95209c7..4e9370c 100644
--- a/core/java/android/webkit/TextDialog.java
+++ b/core/java/android/webkit/TextDialog.java
@@ -124,6 +124,10 @@ import android.widget.AutoCompleteTextView;
int flags = paint.getFlags() | Paint.SUBPIXEL_TEXT_FLAG |
Paint.ANTI_ALIAS_FLAG & ~Paint.DEV_KERN_TEXT_FLAG;
paint.setFlags(flags);
+ // Set the text color to black, regardless of the theme. This ensures
+ // that other applications that use embedded WebViews will properly
+ // display the text in textfields.
+ setTextColor(Color.BLACK);
}
@Override
@@ -395,6 +399,7 @@ import android.widget.AutoCompleteTextView;
* focus to the host.
*/
/* package */ void remove() {
+ mHandler.removeMessages(LONGPRESS);
mWebView.removeView(this);
mWebView.requestFocus();
}
@@ -532,6 +537,7 @@ import android.widget.AutoCompleteTextView;
mPreChange = text.toString();
Editable edit = (Editable) getText();
edit.replace(0, edit.length(), text);
+ updateCachedTextfield();
}
/**
diff --git a/core/java/android/webkit/URLUtil.java b/core/java/android/webkit/URLUtil.java
index 43666c1..0e8144e 100644
--- a/core/java/android/webkit/URLUtil.java
+++ b/core/java/android/webkit/URLUtil.java
@@ -145,6 +145,7 @@ public final class URLUtil {
/**
* @return True iff the url is an proxy url to allow cookieless network
* requests from a file url.
+ * @deprecated Cookieless proxy is no longer supported.
*/
public static boolean isCookielessProxyUrl(String url) {
return (null != url) && url.startsWith(PROXY_BASE);
diff --git a/core/java/android/webkit/WebBackForwardList.java b/core/java/android/webkit/WebBackForwardList.java
index c86b21d..9dea5ec 100644
--- a/core/java/android/webkit/WebBackForwardList.java
+++ b/core/java/android/webkit/WebBackForwardList.java
@@ -133,7 +133,7 @@ public class WebBackForwardList implements Cloneable, Serializable {
}
/* Remove the item at the given index. Called by JNI only. */
- private void removeHistoryItem(int index) {
+ private synchronized void removeHistoryItem(int index) {
// XXX: This is a special case. Since the callback is only triggered
// when removing the first item, we can assert that the index is 0.
// This lets us change the current index without having to query the
diff --git a/core/java/android/webkit/WebHistoryItem.java b/core/java/android/webkit/WebHistoryItem.java
index 5570af8..a408e06 100644
--- a/core/java/android/webkit/WebHistoryItem.java
+++ b/core/java/android/webkit/WebHistoryItem.java
@@ -33,6 +33,8 @@ public class WebHistoryItem implements Cloneable {
private String mTitle;
// The base url of this item.
private String mUrl;
+ // The original requested url of this item.
+ private String mOriginalUrl;
// The favicon for this item.
private Bitmap mFavicon;
// The pre-flattened data used for saving the state.
@@ -95,6 +97,18 @@ public class WebHistoryItem implements Cloneable {
}
/**
+ * Return the original url of this history item. This was the requested
+ * url, the final url may be different as there might have been
+ * redirects while loading the site.
+ * @return The original url of this history item.
+ *
+ * @hide pending API Council approval
+ */
+ public String getOriginalUrl() {
+ return mOriginalUrl;
+ }
+
+ /**
* Return the document title of this history item.
* @return The document title of this history item.
* Note: The VM ensures 32-bit atomic read/write operations so we don't have
@@ -154,8 +168,10 @@ public class WebHistoryItem implements Cloneable {
private native void inflate(int nativeFrame, byte[] data);
/* Called by jni when the item is updated */
- private void update(String url, String title, Bitmap favicon, byte[] data) {
+ private void update(String url, String originalUrl, String title,
+ Bitmap favicon, byte[] data) {
mUrl = url;
+ mOriginalUrl = originalUrl;
mTitle = title;
mFavicon = favicon;
mFlattenedData = data;
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index de64b30..1a7c4ff 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -20,6 +20,8 @@ import android.content.Context;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
+import java.lang.SecurityException;
+import android.content.pm.PackageManager;
import java.util.Locale;
@@ -111,6 +113,7 @@ public class WebSettings {
// retrieve the values. After setXXX, postSync() needs to be called.
// XXX: The default values need to match those in WebSettings.cpp
private LayoutAlgorithm mLayoutAlgorithm = LayoutAlgorithm.NARROW_COLUMNS;
+ private Context mContext;
private TextSize mTextSize = TextSize.NORMAL;
private String mStandardFontFamily = "sans-serif";
private String mFixedFontFamily = "monospace";
@@ -119,7 +122,9 @@ public class WebSettings {
private String mCursiveFontFamily = "cursive";
private String mFantasyFontFamily = "fantasy";
private String mDefaultTextEncoding = "Latin-1";
- private String mUserAgent = ANDROID_USERAGENT;
+ private String mUserAgent;
+ private boolean mUseDefaultUserAgent;
+ private String mAcceptLanguage;
private String mPluginsPath = "";
private int mMinimumFontSize = 8;
private int mMinimumLogicalFontSize = 8;
@@ -127,12 +132,14 @@ public class WebSettings {
private int mDefaultFixedFontSize = 13;
private boolean mLoadsImagesAutomatically = true;
private boolean mBlockNetworkImage = false;
+ private boolean mBlockNetworkLoads = false;
private boolean mJavaScriptEnabled = false;
private boolean mPluginsEnabled = false;
private boolean mJavaScriptCanOpenWindowsAutomatically = false;
private boolean mUseDoubleTree = false;
private boolean mUseWideViewport = false;
private boolean mSupportMultipleWindows = false;
+ private boolean mShrinksStandaloneImagesToFit = false;
// Don't need to synchronize the get/set methods as they
// are basic types, also none of these values are used in
// native WebCore code.
@@ -144,6 +151,7 @@ public class WebSettings {
private boolean mNeedInitialFocus = true;
private boolean mNavDump = false;
private boolean mSupportZoom = true;
+ private boolean mAllowFileAccess = true;
// Class to handle messages before WebCore is ready.
private class EventHandler {
@@ -212,57 +220,112 @@ public class WebSettings {
// User agent strings.
private static final String DESKTOP_USERAGENT =
- "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en) AppleWebKit/522+ " +
- "(KHTML, like Gecko) Safari/419.3";
- private static final String IPHONE_USERAGENT = "Mozilla/5.0 (iPhone; U; " +
- "CPU like Mac OS X; en) AppleWebKit/420+ (KHTML, like Gecko) " +
- "Version/3.0 Mobile/1A543 Safari/419.3";
- private static String ANDROID_USERAGENT;
-
+ "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en)"
+ + " AppleWebKit/528.5+ (KHTML, like Gecko) Version/3.1.2"
+ + " Safari/525.20.1";
+ private static final String IPHONE_USERAGENT =
+ "Mozilla/5.0 (iPhone; U; CPU iPhone 2_1 like Mac OS X; en)"
+ + " AppleWebKit/528.5+ (KHTML, like Gecko) Version/3.1.2"
+ + " Mobile/5F136 Safari/525.20.1";
+ private static Locale sLocale;
+ private static Object sLockForLocaleSettings;
+
/**
* Package constructor to prevent clients from creating a new settings
* instance.
*/
- WebSettings(Context context) {
- if (ANDROID_USERAGENT == null) {
- StringBuffer arg = new StringBuffer();
- // Add version
- final String version = Build.VERSION.RELEASE;
- if (version.length() > 0) {
- arg.append(version);
- } else {
- // default to "1.0"
- arg.append("1.0");
+ WebSettings(Context context) {
+ mEventHandler = new EventHandler();
+ mContext = context;
+
+ if (sLockForLocaleSettings == null) {
+ sLockForLocaleSettings = new Object();
+ sLocale = Locale.getDefault();
+ }
+ mAcceptLanguage = getCurrentAcceptLanguage();
+ mUserAgent = getCurrentUserAgent();
+ mUseDefaultUserAgent = true;
+
+ verifyNetworkAccess();
+ }
+
+ /**
+ * Looks at sLocale and returns current AcceptLanguage String.
+ * @return Current AcceptLanguage String.
+ */
+ private String getCurrentAcceptLanguage() {
+ Locale locale;
+ synchronized(sLockForLocaleSettings) {
+ locale = sLocale;
+ }
+ StringBuffer buffer = new StringBuffer();
+ final String language = locale.getLanguage();
+ if (language != null) {
+ buffer.append(language);
+ final String country = locale.getCountry();
+ if (country != null) {
+ buffer.append("-");
+ buffer.append(country);
}
- arg.append("; ");
- // Initialize the mobile user agent with the default locale.
- final Locale l = Locale.getDefault();
- final String language = l.getLanguage();
- if (language != null) {
- arg.append(language.toLowerCase());
- final String country = l.getCountry();
+ }
+ if (!locale.equals(Locale.US)) {
+ buffer.append(", ");
+ java.util.Locale us = Locale.US;
+ if (us.getLanguage() != null) {
+ buffer.append(us.getLanguage());
+ final String country = us.getCountry();
if (country != null) {
- arg.append("-");
- arg.append(country.toLowerCase());
+ buffer.append("-");
+ buffer.append(country);
}
- } else {
- // default to "en"
- arg.append("en");
}
- // Add device name
- final String device = Build.DEVICE;
- if (device.length() > 0) {
- arg.append("; ");
- arg.append(device);
+ }
+
+ return buffer.toString();
+ }
+
+ /**
+ * Looks at sLocale and mContext and returns current UserAgent String.
+ * @return Current UserAgent String.
+ */
+ private synchronized String getCurrentUserAgent() {
+ Locale locale;
+ synchronized(sLockForLocaleSettings) {
+ locale = sLocale;
+ }
+ StringBuffer buffer = new StringBuffer();
+ // Add version
+ final String version = Build.VERSION.RELEASE;
+ if (version.length() > 0) {
+ buffer.append(version);
+ } else {
+ // default to "1.0"
+ buffer.append("1.0");
+ }
+ buffer.append("; ");
+ final String language = locale.getLanguage();
+ if (language != null) {
+ buffer.append(language.toLowerCase());
+ final String country = locale.getCountry();
+ if (country != null) {
+ buffer.append("-");
+ buffer.append(country.toLowerCase());
}
- final String base = context.getResources().getText(
- com.android.internal.R.string.web_user_agent).toString();
- ANDROID_USERAGENT = String.format(base, arg);
- mUserAgent = ANDROID_USERAGENT;
+ } else {
+ // default to "en"
+ buffer.append("en");
}
- mEventHandler = new EventHandler();
+
+ final String device = Build.DEVICE;
+ if (device.length() > 0) {
+ buffer.append("; ");
+ buffer.append(device);
+ }
+ final String base = mContext.getResources().getText(
+ com.android.internal.R.string.web_user_agent).toString();
+ return String.format(base, buffer);
}
-
+
/**
* Enables dumping the pages navigation cache to a text file.
*/
@@ -292,6 +355,21 @@ public class WebSettings {
}
/**
+ * Enable or disable file access within WebView. File access is enabled by
+ * default.
+ */
+ public void setAllowFileAccess(boolean allow) {
+ mAllowFileAccess = allow;
+ }
+
+ /**
+ * Returns true if this WebView supports file access.
+ */
+ public boolean getAllowFileAccess() {
+ return mAllowFileAccess;
+ }
+
+ /**
* Store whether the WebView is saving form data.
*/
public void setSaveFormData(boolean save) {
@@ -377,34 +455,48 @@ public class WebSettings {
* Tell the WebView about user-agent string.
* @param ua 0 if the WebView should use an Android user-agent string,
* 1 if the WebView should use a desktop user-agent string.
- * 2 if the WebView should use an iPhone user-agent string.
+ *
+ * @deprecated Please use setUserAgentString instead.
*/
+ @Deprecated
public synchronized void setUserAgent(int ua) {
- if (ua == 0 && !ANDROID_USERAGENT.equals(mUserAgent)) {
- mUserAgent = ANDROID_USERAGENT;
- postSync();
- } else if (ua == 1 && !DESKTOP_USERAGENT.equals(mUserAgent)) {
- mUserAgent = DESKTOP_USERAGENT;
- postSync();
- } else if (ua == 2 && !IPHONE_USERAGENT.equals(mUserAgent)) {
- mUserAgent = IPHONE_USERAGENT;
- postSync();
+ String uaString = null;
+ if (ua == 1) {
+ if (DESKTOP_USERAGENT.equals(mUserAgent)) {
+ return; // do nothing
+ } else {
+ uaString = DESKTOP_USERAGENT;
+ }
+ } else if (ua == 2) {
+ if (IPHONE_USERAGENT.equals(mUserAgent)) {
+ return; // do nothing
+ } else {
+ uaString = IPHONE_USERAGENT;
+ }
+ } else if (ua != 0) {
+ return; // do nothing
}
+ setUserAgentString(uaString);
}
/**
* Return user-agent as int
* @return int 0 if the WebView is using an Android user-agent string.
* 1 if the WebView is using a desktop user-agent string.
- * 2 if the WebView is using an iPhone user-agent string.
+ * -1 if the WebView is using user defined user-agent string.
+ *
+ * @deprecated Please use getUserAgentString instead.
*/
+ @Deprecated
public synchronized int getUserAgent() {
if (DESKTOP_USERAGENT.equals(mUserAgent)) {
return 1;
} else if (IPHONE_USERAGENT.equals(mUserAgent)) {
return 2;
+ } else if (mUseDefaultUserAgent) {
+ return 0;
}
- return 0;
+ return -1;
}
/**
@@ -706,6 +798,40 @@ public class WebSettings {
public synchronized boolean getBlockNetworkImage() {
return mBlockNetworkImage;
}
+
+ /**
+ * @hide
+ * Tell the WebView to block all network load requests.
+ * @param flag True if the WebView should block all network loads
+ */
+ public synchronized void setBlockNetworkLoads(boolean flag) {
+ if (mBlockNetworkLoads != flag) {
+ mBlockNetworkLoads = flag;
+ verifyNetworkAccess();
+ }
+ }
+
+ /**
+ * @hide
+ * Return true if the WebView will block all network loads.
+ * @return True if the WebView blocks all network loads.
+ */
+ public synchronized boolean getBlockNetworkLoads() {
+ return mBlockNetworkLoads;
+ }
+
+
+ private void verifyNetworkAccess() {
+ if (!mBlockNetworkLoads) {
+ if (mContext.checkPermission("android.permission.INTERNET",
+ android.os.Process.myPid(), 0) !=
+ PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException
+ ("Permission denied - " +
+ "application missing INTERNET permission");
+ }
+ }
+ }
/**
* Tell the WebView to enable javascript execution.
@@ -806,11 +932,69 @@ public class WebSettings {
return mDefaultTextEncoding;
}
- /* Package api to grab the user agent string. */
- /*package*/ synchronized String getUserAgentString() {
+ /**
+ * Set the WebView's user-agent string. If the string "ua" is null or empty,
+ * it will use the system default user-agent string.
+ */
+ public synchronized void setUserAgentString(String ua) {
+ if (ua == null || ua.length() == 0) {
+ synchronized(sLockForLocaleSettings) {
+ Locale currentLocale = Locale.getDefault();
+ if (!sLocale.equals(currentLocale)) {
+ sLocale = currentLocale;
+ mAcceptLanguage = getCurrentAcceptLanguage();
+ }
+ }
+ ua = getCurrentUserAgent();
+ mUseDefaultUserAgent = true;
+ } else {
+ mUseDefaultUserAgent = false;
+ }
+
+ if (!ua.equals(mUserAgent)) {
+ mUserAgent = ua;
+ postSync();
+ }
+ }
+
+ /**
+ * Return the WebView's user-agent string.
+ */
+ public synchronized String getUserAgentString() {
+ if (DESKTOP_USERAGENT.equals(mUserAgent) ||
+ IPHONE_USERAGENT.equals(mUserAgent) ||
+ !mUseDefaultUserAgent) {
+ return mUserAgent;
+ }
+
+ boolean doPostSync = false;
+ synchronized(sLockForLocaleSettings) {
+ Locale currentLocale = Locale.getDefault();
+ if (!sLocale.equals(currentLocale)) {
+ sLocale = currentLocale;
+ mUserAgent = getCurrentUserAgent();
+ mAcceptLanguage = getCurrentAcceptLanguage();
+ doPostSync = true;
+ }
+ }
+ if (doPostSync) {
+ postSync();
+ }
return mUserAgent;
}
+ /* package api to grab the Accept Language string. */
+ /*package*/ synchronized String getAcceptLanguage() {
+ synchronized(sLockForLocaleSettings) {
+ Locale currentLocale = Locale.getDefault();
+ if (!sLocale.equals(currentLocale)) {
+ sLocale = currentLocale;
+ mAcceptLanguage = getCurrentAcceptLanguage();
+ }
+ }
+ return mAcceptLanguage;
+ }
+
/**
* Tell the WebView whether it needs to set a node to have focus when
* {@link WebView#requestFocus(int, android.graphics.Rect)} is called.
@@ -863,6 +1047,20 @@ public class WebSettings {
public int getCacheMode() {
return mOverrideCacheMode;
}
+
+ /**
+ * If set, webkit alternately shrinks and expands images viewed outside
+ * of an HTML page to fit the screen. This conflicts with attempts by
+ * the UI to zoom in and out of an image, so it is set false by default.
+ * @param shrink Set true to let webkit shrink the standalone image to fit.
+ * {@hide}
+ */
+ public void setShrinksStandaloneImagesToFit(boolean shrink) {
+ if (mShrinksStandaloneImagesToFit != shrink) {
+ mShrinksStandaloneImagesToFit = shrink;
+ postSync();
+ }
+ }
/**
* Transfer messages from the queue to the new WebCoreThread. Called from
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 6623257..7467b83 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -56,6 +56,7 @@ import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.ViewTreeObserver;
+import android.view.inputmethod.InputMethodManager;
import android.webkit.WebViewCore.EventHub;
import android.widget.AbsoluteLayout;
import android.widget.AdapterView;
@@ -94,6 +95,12 @@ public class WebView extends AbsoluteLayout
implements ViewTreeObserver.OnGlobalFocusChangeListener,
ViewGroup.OnHierarchyChangeListener {
+ // if AUTO_REDRAW_HACK is true, then the CALL key will toggle redrawing
+ // the screen all-the-time. Good for profiling our drawing code
+ static private final boolean AUTO_REDRAW_HACK = false;
+ // true means redraw the screen all-the-time. Only with AUTO_REDRAW_HACK
+ private boolean mAutoRedraw;
+
// keep debugging parameters near the top of the file
static final String LOGTAG = "webview";
static final boolean DEBUG = false;
@@ -112,7 +119,8 @@ public class WebView extends AbsoluteLayout
(com.android.internal.R.id.zoomMagnify);
}
- public void show(boolean canZoomOut) {
+ public void show(boolean showZoom, boolean canZoomOut) {
+ mZoomControls.setVisibility(showZoom ? View.VISIBLE : View.GONE);
mZoomMagnify.setVisibility(canZoomOut ? View.VISIBLE : View.GONE);
fade(View.VISIBLE, 0.0f, 1.0f);
}
@@ -211,6 +219,17 @@ public class WebView extends AbsoluteLayout
private long mLastTouchTime;
/**
+ * Time of the last time sending touch event to WebViewCore
+ */
+ private long mLastSentTouchTime;
+
+ /**
+ * The minimum elapsed time before sending another ACTION_MOVE event to
+ * WebViewCore
+ */
+ private static final int TOUCH_SENT_INTERVAL = 100;
+
+ /**
* Helper class to get velocity for fling
*/
VelocityTracker mVelocityTracker;
@@ -226,6 +245,7 @@ public class WebView extends AbsoluteLayout
private static final int TOUCH_SHORTPRESS_MODE = 5;
private static final int TOUCH_DOUBLECLICK_MODE = 6;
private static final int TOUCH_DONE_MODE = 7;
+ private static final int TOUCH_SELECT_MODE = 8;
// touch mode values specific to scale+scroll
private static final int FIRST_SCROLL_ZOOM = 9;
private static final int SCROLL_ZOOM_ANIMATION_IN = 9;
@@ -234,6 +254,9 @@ public class WebView extends AbsoluteLayout
private static final int LAST_SCROLL_ZOOM = 11;
// end of touch mode values specific to scale+scroll
+ // Whether to forward the touch events to WebCore
+ private boolean mForwardTouchEvents = false;
+
// If updateTextEntry gets called while we are out of focus, use this
// variable to remember to do it next time we gain focus.
private boolean mNeedsUpdateTextEntry = false;
@@ -337,7 +360,9 @@ public class WebView extends AbsoluteLayout
static final int NOTIFY_FOCUS_SET_MSG_ID = 20;
static final int MARK_NODE_INVALID_ID = 21;
static final int UPDATE_CLIPBOARD = 22;
- static final int LONG_PRESS_TRACKBALL = 24;
+ static final int LONG_PRESS_ENTER = 23;
+ static final int PREVENT_TOUCH_ID = 24;
+ static final int WEBCORE_NEED_TOUCH_EVENTS = 25;
// width which view is considered to be fully zoomed out
static final int ZOOM_OUT_WIDTH = 1024;
@@ -524,23 +549,23 @@ public class WebView extends AbsoluteLayout
setVerticalScrollBarEnabled(true);
}
- /* package */ boolean onSavePassword(String host, String username,
+ /* package */ boolean onSavePassword(String schemePlusHost, String username,
String password, final Message resumeMsg) {
boolean rVal = false;
if (resumeMsg == null) {
// null resumeMsg implies saving password silently
- mDatabase.setUsernamePassword(host, username, password);
+ mDatabase.setUsernamePassword(schemePlusHost, username, password);
} else {
final Message remember = mPrivateHandler.obtainMessage(
REMEMBER_PASSWORD);
- remember.getData().putString("host", host);
+ remember.getData().putString("host", schemePlusHost);
remember.getData().putString("username", username);
remember.getData().putString("password", password);
remember.obj = resumeMsg;
final Message neverRemember = mPrivateHandler.obtainMessage(
NEVER_REMEMBER_PASSWORD);
- neverRemember.getData().putString("host", host);
+ neverRemember.getData().putString("host", schemePlusHost);
neverRemember.getData().putString("username", username);
neverRemember.getData().putString("password", password);
neverRemember.obj = resumeMsg;
@@ -749,14 +774,36 @@ public class WebView extends AbsoluteLayout
public static void disablePlatformNotifications() {
Network.disablePlatformNotifications();
}
+
+ /**
+ * Inform WebView of the network state. This is used to set
+ * the javascript property window.navigator.isOnline and
+ * generates the online/offline event as specified in HTML5, sec. 5.7.7
+ * @param networkUp boolean indicating if network is available
+ *
+ * @hide pending API Council approval
+ */
+ public void setNetworkAvailable(boolean networkUp) {
+ BrowserFrame.sJavaBridge.setNetworkOnLine(networkUp);
+ }
/**
- * Save the state of this WebView used in Activity.onSaveInstanceState.
+ * Save the state of this WebView used in
+ * {@link android.app.Activity#onSaveInstanceState}. Please note that this
+ * method no longer stores the display data for this WebView. The previous
+ * behavior could potentially leak files if {@link #restoreState} was never
+ * called. See {@link #savePicture} and {@link #restorePicture} for saving
+ * and restoring the display data.
* @param outState The Bundle to store the WebView state.
* @return The same copy of the back/forward list used to save the state. If
* saveState fails, the returned list will be null.
+ * @see #savePicture
+ * @see #restorePicture
*/
public WebBackForwardList saveState(Bundle outState) {
+ if (outState == null) {
+ return null;
+ }
// We grab a copy of the back/forward list because a client of WebView
// may have invalidated the history list by calling clearHistory.
WebBackForwardList list = copyBackForwardList();
@@ -782,29 +829,6 @@ public class WebView extends AbsoluteLayout
return null;
}
history.add(data);
- if (i == currentIndex) {
- Picture p = capturePicture();
- String path = mContext.getDir("thumbnails", 0).getPath()
- + File.separatorChar + hashCode() + "_pic.save";
- File f = new File(path);
- try {
- final FileOutputStream out = new FileOutputStream(f);
- p.writeToStream(out);
- out.close();
- } catch (FileNotFoundException e){
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- } catch (RuntimeException e) {
- e.printStackTrace();
- }
- if (f.length() > 0) {
- outState.putString("picture", path);
- outState.putInt("scrollX", mScrollX);
- outState.putInt("scrollY", mScrollY);
- outState.putFloat("scale", mActualScale);
- }
- }
}
outState.putSerializable("history", history);
if (mCertificate != null) {
@@ -815,16 +839,103 @@ public class WebView extends AbsoluteLayout
}
/**
+ * Save the current display data to the Bundle given. Used in conjunction
+ * with {@link #saveState}.
+ * @param b A Bundle to store the display data.
+ * @param dest The file to store the serialized picture data. Will be
+ * overwritten with this WebView's picture data.
+ * @return True if the picture was successfully saved.
+ */
+ public boolean savePicture(Bundle b, File dest) {
+ if (dest == null || b == null) {
+ return false;
+ }
+ final Picture p = capturePicture();
+ try {
+ final FileOutputStream out = new FileOutputStream(dest);
+ p.writeToStream(out);
+ out.close();
+ } catch (FileNotFoundException e){
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ } catch (RuntimeException e) {
+ e.printStackTrace();
+ }
+ if (dest.length() > 0) {
+ b.putInt("scrollX", mScrollX);
+ b.putInt("scrollY", mScrollY);
+ b.putFloat("scale", mActualScale);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Restore the display data that was save in {@link #savePicture}. Used in
+ * conjunction with {@link #restoreState}.
+ * @param b A Bundle containing the saved display data.
+ * @param src The file where the picture data was stored.
+ * @return True if the picture was successfully restored.
+ */
+ public boolean restorePicture(Bundle b, File src) {
+ if (src == null || b == null) {
+ return false;
+ }
+ if (src.exists()) {
+ Picture p = null;
+ try {
+ final FileInputStream in = new FileInputStream(src);
+ p = Picture.createFromStream(in);
+ in.close();
+ } catch (FileNotFoundException e){
+ e.printStackTrace();
+ } catch (RuntimeException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ if (p != null) {
+ int sx = b.getInt("scrollX", 0);
+ int sy = b.getInt("scrollY", 0);
+ float scale = b.getFloat("scale", 1.0f);
+ mDrawHistory = true;
+ mHistoryPicture = p;
+ mScrollX = sx;
+ mScrollY = sy;
+ mHistoryWidth = Math.round(p.getWidth() * scale);
+ mHistoryHeight = Math.round(p.getHeight() * scale);
+ // as getWidth() / getHeight() of the view are not
+ // available yet, set up mActualScale, so that when
+ // onSizeChanged() is called, the rest will be set
+ // correctly
+ mActualScale = scale;
+ invalidate();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
* Restore the state of this WebView from the given map used in
- * Activity.onThaw. This method should be called to restore the state of
- * the WebView before using the object. If it is called after the WebView
- * has had a chance to build state (load pages, create a back/forward list,
- * etc.) there may be undesirable side-effects.
+ * {@link android.app.Activity#onRestoreInstanceState}. This method should
+ * be called to restore the state of the WebView before using the object. If
+ * it is called after the WebView has had a chance to build state (load
+ * pages, create a back/forward list, etc.) there may be undesirable
+ * side-effects. Please note that this method no longer restores the
+ * display data for this WebView. See {@link #savePicture} and {@link
+ * #restorePicture} for saving and restoring the display data.
* @param inState The incoming Bundle of state.
* @return The restored back/forward list or null if restoreState failed.
+ * @see #savePicture
+ * @see #restorePicture
*/
public WebBackForwardList restoreState(Bundle inState) {
WebBackForwardList returnList = null;
+ if (inState == null) {
+ return returnList;
+ }
if (inState.containsKey("index") && inState.containsKey("history")) {
mCertificate = SslCertificate.restoreState(
inState.getBundle("certificate"));
@@ -853,42 +964,6 @@ public class WebView extends AbsoluteLayout
WebHistoryItem item = new WebHistoryItem(data);
list.addHistoryItem(item);
}
- if (inState.containsKey("picture")) {
- String path = inState.getString("picture");
- File f = new File(path);
- if (f.exists()) {
- Picture p = null;
- try {
- final FileInputStream in = new FileInputStream(f);
- p = Picture.createFromStream(in);
- in.close();
- f.delete();
- } catch (FileNotFoundException e){
- e.printStackTrace();
- } catch (RuntimeException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
- if (p != null) {
- int sx = inState.getInt("scrollX", 0);
- int sy = inState.getInt("scrollY", 0);
- float scale = inState.getFloat("scale", 1.0f);
- mDrawHistory = true;
- mHistoryPicture = p;
- mScrollX = sx;
- mScrollY = sy;
- mHistoryWidth = Math.round(p.getWidth() * scale);
- mHistoryHeight = Math.round(p.getHeight() * scale);
- // as getWidth() / getHeight() of the view are not
- // available yet, set up mActualScale, so that when
- // onSizeChanged() is called, the rest will be set
- // correctly
- mActualScale = scale;
- invalidate();
- }
- }
- }
// Grab the most recent copy to return to the caller.
returnList = copyBackForwardList();
// Update the copy to have the correct index.
@@ -929,14 +1004,22 @@ public class WebView extends AbsoluteLayout
* Load the given data into the WebView, use the provided URL as the base
* URL for the content. The base URL is the URL that represents the page
* that is loaded through this interface. As such, it is used for the
- * history entry and to resolve any relative URLs.
- * The failUrl is used if browser fails to load the data provided. If it
- * is empty or null, and the load fails, then no history entry is created.
+ * history entry and to resolve any relative URLs. The failUrl is used if
+ * browser fails to load the data provided. If it is empty or null, and the
+ * load fails, then no history entry is created.
+ * <p>
+ * Note for post 1.0. Due to the change in the WebKit, the access to asset
+ * files through "file:///android_asset/" for the sub resources is more
+ * restricted. If you provide null or empty string as baseUrl, you won't be
+ * able to access asset files. If the baseUrl is anything other than
+ * http(s)/ftp(s)/about/javascript as scheme, you can access asset files for
+ * sub resources.
+ *
* @param baseUrl Url to resolve relative paths with, if null defaults to
- * "about:blank"
+ * "about:blank"
* @param data A String of data in the given encoding.
* @param mimeType The MIMEType of the data. i.e. text/html. If null,
- * defaults to "text/html"
+ * defaults to "text/html"
* @param encoding The encoding of the data. i.e. utf-8, us-ascii
* @param failUrl URL to use if the content fails to load or null.
*/
@@ -1133,7 +1216,7 @@ public class WebView extends AbsoluteLayout
public void clearView() {
mContentWidth = 0;
mContentHeight = 0;
- mWebViewCore.clearContentPicture();
+ mWebViewCore.sendMessage(EventHub.CLEAR_CONTENT);
}
/**
@@ -1197,7 +1280,7 @@ public class WebView extends AbsoluteLayout
clearTextEntry();
ExtendedZoomControls zoomControls = (ExtendedZoomControls)
getZoomControls();
- zoomControls.show(canZoomScrollOut());
+ zoomControls.show(true, canZoomScrollOut());
zoomControls.requestFocus();
mPrivateHandler.removeCallbacks(mZoomControlRunnable);
mPrivateHandler.postDelayed(mZoomControlRunnable,
@@ -1206,11 +1289,15 @@ public class WebView extends AbsoluteLayout
/**
* Return a HitTestResult based on the current focus node. If a HTML::a tag
- * is found, the HitTestResult type is set to ANCHOR_TYPE and the url has to
- * be retrieved through {@link #requestFocusNodeHref} asynchronously. If a
- * HTML::img tag is found, the HitTestResult type is set to IMAGE_TYPE and
- * the url has to be retrieved through {@link #requestFocusNodeHref}
- * asynchronously. If a phone number is found, the HitTestResult type is set
+ * is found and the anchor has a non-javascript url, the HitTestResult type
+ * is set to SRC_ANCHOR_TYPE and the url is set in the "extra" field. If the
+ * anchor does not have a url or if it is a javascript url, the type will
+ * be UNKNOWN_TYPE and the url has to be retrieved through
+ * {@link #requestFocusNodeHref} asynchronously. If a HTML::img tag is
+ * found, the HitTestResult type is set to IMAGE_TYPE and the url is set in
+ * the "extra" field. A type of
+ * SRC_IMAGE_ANCHOR_TYPE indicates an anchor with a url that has an image as
+ * a child node. If a phone number is found, the HitTestResult type is set
* to PHONE_TYPE and the phone number is set in the "extra" field of
* HitTestResult. If a map address is found, the HitTestResult type is set
* to GEO_TYPE and the address is set in the "extra" field of HitTestResult.
@@ -1227,42 +1314,38 @@ public class WebView extends AbsoluteLayout
if (nativeUpdateFocusNode()) {
FocusNode node = mFocusNode;
- if (node.mIsAnchor && node.mText == null) {
- result.setType(HitTestResult.ANCHOR_TYPE);
- } else if (node.mIsTextField || node.mIsTextArea) {
+ if (node.mIsTextField || node.mIsTextArea) {
result.setType(HitTestResult.EDIT_TEXT_TYPE);
- } else {
+ } else if (node.mText != null) {
String text = node.mText;
- if (text != null) {
- if (text.startsWith(SCHEME_TEL)) {
- result.setType(HitTestResult.PHONE_TYPE);
- result.setExtra(text.substring(SCHEME_TEL.length()));
- } else if (text.startsWith(SCHEME_MAILTO)) {
- result.setType(HitTestResult.EMAIL_TYPE);
- result.setExtra(text.substring(SCHEME_MAILTO.length()));
- } else if (text.startsWith(SCHEME_GEO)) {
- result.setType(HitTestResult.GEO_TYPE);
- result.setExtra(URLDecoder.decode(text
- .substring(SCHEME_GEO.length())));
- }
+ if (text.startsWith(SCHEME_TEL)) {
+ result.setType(HitTestResult.PHONE_TYPE);
+ result.setExtra(text.substring(SCHEME_TEL.length()));
+ } else if (text.startsWith(SCHEME_MAILTO)) {
+ result.setType(HitTestResult.EMAIL_TYPE);
+ result.setExtra(text.substring(SCHEME_MAILTO.length()));
+ } else if (text.startsWith(SCHEME_GEO)) {
+ result.setType(HitTestResult.GEO_TYPE);
+ result.setExtra(URLDecoder.decode(text
+ .substring(SCHEME_GEO.length())));
+ } else if (node.mIsAnchor) {
+ result.setType(HitTestResult.SRC_ANCHOR_TYPE);
+ result.setExtra(text);
}
}
}
int type = result.getType();
if (type == HitTestResult.UNKNOWN_TYPE
- || type == HitTestResult.ANCHOR_TYPE) {
+ || type == HitTestResult.SRC_ANCHOR_TYPE) {
// Now check to see if it is an image.
int contentX = viewToContent((int) mLastTouchX + mScrollX);
int contentY = viewToContent((int) mLastTouchY + mScrollY);
- if (nativeIsImage(contentX, contentY)) {
+ String text = nativeImageURI(contentX, contentY);
+ if (text != null) {
result.setType(type == HitTestResult.UNKNOWN_TYPE ?
HitTestResult.IMAGE_TYPE :
- HitTestResult.IMAGE_ANCHOR_TYPE);
- }
- if (nativeHasSrcUrl()) {
- result.setType(result.getType() == HitTestResult.ANCHOR_TYPE ?
- HitTestResult.SRC_ANCHOR_TYPE :
HitTestResult.SRC_IMAGE_ANCHOR_TYPE);
+ result.setExtra(text);
}
}
return result;
@@ -1284,12 +1367,15 @@ public class WebView extends AbsoluteLayout
if (nativeUpdateFocusNode()) {
FocusNode node = mFocusNode;
if (node.mIsAnchor) {
+ // NOTE: We may already have the url of the anchor stored in
+ // node.mText but it may be out of date or the caller may want
+ // to know about javascript urls.
mWebViewCore.sendMessage(EventHub.REQUEST_FOCUS_HREF,
node.mFramePointer, node.mNodePointer, hrefMsg);
}
}
}
-
+
/**
* Request the url of the image last touched by the user. msg will be sent
* to its target with a String representing the url as its object.
@@ -1298,13 +1384,13 @@ public class WebView extends AbsoluteLayout
* as the data member with "url" as key. The result can be null.
*/
public void requestImageRef(Message msg) {
- if (msg == null || mNativeClass == 0) {
- return;
- }
int contentX = viewToContent((int) mLastTouchX + mScrollX);
int contentY = viewToContent((int) mLastTouchY + mScrollY);
- mWebViewCore.sendMessage(EventHub.REQUEST_IMAGE_HREF, contentX,
- contentY, msg);
+ String ref = nativeImageURI(contentX, contentY);
+ Bundle data = msg.getData();
+ data.putString("url", ref);
+ msg.setData(data);
+ msg.sendToTarget();
}
private static int pinLoc(int x, int viewMax, int docMax) {
@@ -1340,6 +1426,25 @@ public class WebView extends AbsoluteLayout
return Math.round(x * mActualScale);
}
+ // Called by JNI to invalidate the View, given rectangle coordinates in
+ // content space
+ private void viewInvalidate(int l, int t, int r, int b) {
+ invalidate(contentToView(l), contentToView(t), contentToView(r),
+ contentToView(b));
+ }
+
+ // Called by JNI to invalidate the View after a delay, given rectangle
+ // coordinates in content space
+ private void viewInvalidateDelayed(long delay, int l, int t, int r, int b) {
+ postInvalidateDelayed(delay, contentToView(l), contentToView(t),
+ contentToView(r), contentToView(b));
+ }
+
+ private Rect contentToView(Rect x) {
+ return new Rect(contentToView(x.left), contentToView(x.top)
+ , contentToView(x.right), contentToView(x.bottom));
+ }
+
/* call from webcoreview.draw(), so we're still executing in the UI thread
*/
private void recordNewContentSize(int w, int h, boolean updateLayout) {
@@ -1420,15 +1525,29 @@ public class WebView extends AbsoluteLayout
// Used to avoid sending many visible rect messages.
private Rect mLastVisibleRectSent;
+ private Rect mLastGlobalRect;
private Rect sendOurVisibleRect() {
Rect rect = new Rect();
calcOurContentVisibleRect(rect);
+ if (mFindIsUp) {
+ rect.bottom -= viewToContent(FIND_HEIGHT);
+ }
// Rect.equals() checks for null input.
if (!rect.equals(mLastVisibleRectSent)) {
- mWebViewCore.sendMessage(EventHub.SET_VISIBLE_RECT, rect);
+ mWebViewCore.sendMessage(EventHub.SET_SCROLL_OFFSET,
+ rect.left, rect.top);
mLastVisibleRectSent = rect;
}
+ Rect globalRect = new Rect();
+ if (getGlobalVisibleRect(globalRect)
+ && !globalRect.equals(mLastGlobalRect)) {
+ // TODO: the global offset is only used by windowRect()
+ // in ChromeClientAndroid ; other clients such as touch
+ // and mouse events could return view + screen relative points.
+ mWebViewCore.sendMessage(EventHub.SET_GLOBAL_BOUNDS, globalRect);
+ mLastGlobalRect = globalRect;
+ }
return rect;
}
@@ -1488,12 +1607,19 @@ public class WebView extends AbsoluteLayout
}
}
+ // Make sure this stays in sync with the actual height of the FindDialog.
+ private static final int FIND_HEIGHT = 79;
+
@Override
protected int computeVerticalScrollRange() {
if (mDrawHistory) {
return mHistoryHeight;
} else {
- return contentToView(mContentHeight);
+ int height = contentToView(mContentHeight);
+ if (mFindIsUp) {
+ height += FIND_HEIGHT;
+ }
+ return height;
}
}
@@ -1507,6 +1633,21 @@ public class WebView extends AbsoluteLayout
WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
return h != null ? h.getUrl() : null;
}
+
+ /**
+ * Get the original url for the current page. This is not always the same
+ * as the url passed to WebViewClient.onPageStarted because although the
+ * load for that url has begun, the current page may not have changed.
+ * Also, there may have been redirects resulting in a different url to that
+ * originally requested.
+ * @return The url that was originally requested for the current page.
+ *
+ * @hide pending API Council approval
+ */
+ public String getOriginalUrl() {
+ WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
+ return h != null ? h.getOriginalUrl() : null;
+ }
/**
* Get the title for the current page. This is the title of the current page
@@ -1612,82 +1753,35 @@ public class WebView extends AbsoluteLayout
}
/*
- * Making the find methods private since we are disabling for 1.0
- *
- * Find and highlight the next occurance of the String find, beginning with
- * the current selection. Wraps the page infinitely, and scrolls. When
- * WebCore determines whether it has found it, the response message is sent
- * to its target with true(1) or false(0) as arg1 depending on whether the
- * text was found.
- * @param find String to find.
- * @param response A Message object that will be dispatched with the result
- * as the arg1 member. A result of 1 means the search
- * succeeded.
- */
- private void findNext(String find, Message response) {
- if (response == null) {
- return;
- }
- Message m = Message.obtain(null, EventHub.FIND, 1, 0, response);
- m.getData().putString("find", find);
- mWebViewCore.sendMessage(m);
- }
-
- /*
- * Making the find methods private since we are disabling for 1.0
- *
- * Find and highlight the previous occurance of the String find, beginning
- * with the current selection.
- * @param find String to find.
- * @param response A Message object that will be dispatched with the result
- * as the arg1 member. A result of 1 means the search
- * succeeded.
- */
- private void findPrevious(String find, Message response) {
- if (response == null) {
- return;
- }
- Message m = Message.obtain(null, EventHub.FIND, -1, 0, response);
- m.getData().putString("find", find);
- mWebViewCore.sendMessage(m);
- }
-
- /*
- * Making the find methods private since we are disabling for 1.0
+ * Highlight and scroll to the next occurance of String in findAll.
+ * Wraps the page infinitely, and scrolls. Must be called after
+ * calling findAll.
*
- * Find and highlight the first occurance of find, beginning with the start
- * of the page.
- * @param find String to find.
- * @param response A Message object that will be dispatched with the result
- * as the arg1 member. A result of 1 means the search
- * succeeded.
+ * @param forward Direction to search.
*/
- private void findFirst(String find, Message response) {
- if (response == null) {
- return;
- }
- Message m = Message.obtain(null, EventHub.FIND, 0, 0, response);
- m.getData().putString("find", find);
- mWebViewCore.sendMessage(m);
+ public void findNext(boolean forward) {
+ nativeFindNext(forward);
}
/*
- * Making the find methods private since we are disabling for 1.0
- *
* Find all instances of find on the page and highlight them.
* @param find String to find.
- * @param response A Message object that will be dispatched with the result
- * as the arg1 member. The result will be the number of
- * matches to the String find.
+ * @return int The number of occurances of the String "find"
+ * that were found.
*/
- private void findAll(String find, Message response) {
- if (response == null) {
- return;
- }
- Message m = Message.obtain(null, EventHub.FIND_ALL, 0, 0, response);
- m.getData().putString("find", find);
- mWebViewCore.sendMessage(m);
+ public int findAll(String find) {
+ mFindIsUp = true;
+ int result = nativeFindAll(find.toLowerCase(), find.toUpperCase());
+ invalidate();
+ return result;
}
+
+ // Used to know whether the find dialog is open. Affects whether
+ // or not we draw the highlights for matches.
+ private boolean mFindIsUp;
+
+ private native int nativeFindAll(String findLower, String findUpper);
+ private native void nativeFindNext(boolean forward);
/**
* Return the first substring consisting of the address of a physical
@@ -1714,12 +1808,15 @@ public class WebView extends AbsoluteLayout
}
/*
- * Making the find methods private since we are disabling for 1.0
- *
* Clear the highlighting surrounding text matches created by findAll.
*/
- private void clearMatches() {
- mWebViewCore.sendMessage(EventHub.CLEAR_MATCHES);
+ public void clearMatches() {
+ mFindIsUp = false;
+ nativeSetFindIsDown();
+ // Now that the dialog has been removed, ensure that we scroll to a
+ // location that is not beyond the end of the page.
+ pinScrollTo(mScrollX, mScrollY, false);
+ invalidate();
}
/**
@@ -2023,10 +2120,21 @@ public class WebView extends AbsoluteLayout
nativeRecomputeFocus();
// Update the buttons in the picture, so when we draw the picture
// to the screen, they are in the correct state.
- nativeRecordButtons();
+ // Tell the native side if user is a) touching the screen,
+ // b) pressing the trackball down, or c) pressing the enter key
+ // If the focus is a button, we need to draw it in the pressed
+ // state.
+ // If mNativeClass is 0, we should not reach here, so we do not
+ // need to check it again.
+ nativeRecordButtons(mTouchMode == TOUCH_SHORTPRESS_START_MODE
+ || mTrackballDown || mGotEnterDown, false);
drawCoreAndFocusRing(canvas, mBackgroundColor, mDrawFocusRing);
}
canvas.restoreToCount(sc);
+
+ if (AUTO_REDRAW_HACK && mAutoRedraw) {
+ invalidate();
+ }
}
@Override
@@ -2097,7 +2205,12 @@ public class WebView extends AbsoluteLayout
if (mNativeClass == 0) return;
if (mShiftIsPressed) {
- nativeDrawSelection(canvas, mSelectX, mSelectY, mExtendSelection);
+ if (mTouchSelection) {
+ nativeDrawSelectionRegion(canvas);
+ } else {
+ nativeDrawSelection(canvas, mSelectX, mSelectY,
+ mExtendSelection);
+ }
} else if (drawFocus) {
if (mTouchMode == TOUCH_SHORTPRESS_START_MODE) {
mTouchMode = TOUCH_SHORTPRESS_MODE;
@@ -2111,7 +2224,14 @@ public class WebView extends AbsoluteLayout
}
nativeDrawFocusRing(canvas);
}
+ // When the FindDialog is up, only draw the matches if we are not in
+ // the process of scrolling them into view.
+ if (mFindIsUp && !animateScroll) {
+ nativeDrawMatches(canvas);
+ }
}
+
+ private native void nativeDrawMatches(Canvas canvas);
private float scrollZoomGridScale(float invScale) {
float griddedInvScale = (int) (invScale * SCROLL_ZOOM_GRID)
@@ -2163,19 +2283,40 @@ public class WebView extends AbsoluteLayout
Rect scrollFrame = new Rect();
scrollFrame.set(mZoomScrollX, mZoomScrollY,
mZoomScrollX + width, mZoomScrollY + height);
- if (mContentWidth == width) {
- float offsetX = (width * halfScale - width) / 2;
+ if (mContentWidth * mZoomScrollLimit < width) {
+ float scale = zoomFrameScaleX(width, halfScale, 1.0f);
+ float offsetX = (width * scale - width) * 0.5f;
scrollFrame.left -= offsetX;
scrollFrame.right += offsetX;
}
- if (mContentHeight == height) {
- float offsetY = (height * halfScale - height) / 2;
+ if (mContentHeight * mZoomScrollLimit < height) {
+ float scale = zoomFrameScaleY(height, halfScale, 1.0f);
+ float offsetY = (height * scale - height) * 0.5f;
scrollFrame.top -= offsetY;
scrollFrame.bottom += offsetY;
}
return scrollFrame;
}
+ private float zoomFrameScaleX(int width, float halfScale, float noScale) {
+ // mContentWidth > width > mContentWidth * mZoomScrollLimit
+ if (mContentWidth <= width) {
+ return halfScale;
+ }
+ float part = (width - mContentWidth * mZoomScrollLimit)
+ / (width * (1 - mZoomScrollLimit));
+ return halfScale * part + noScale * (1.0f - part);
+ }
+
+ private float zoomFrameScaleY(int height, float halfScale, float noScale) {
+ if (mContentHeight <= height) {
+ return halfScale;
+ }
+ float part = (height - mContentHeight * mZoomScrollLimit)
+ / (height * (1 - mZoomScrollLimit));
+ return halfScale * part + noScale * (1.0f - part);
+ }
+
private float scrollZoomMagScale(float invScale) {
return (invScale * 2 + mInvActualScale) / 3;
}
@@ -2252,8 +2393,14 @@ public class WebView extends AbsoluteLayout
}
int sc = canvas.save();
canvas.clipRect(scrollFrame);
- float halfX = maxX > 0 ? (float) mZoomScrollX / maxX : 0.5f;
- float halfY = maxY > 0 ? (float) mZoomScrollY / maxY : 0.5f;
+ float halfX = (float) mZoomScrollX / maxX;
+ if (mContentWidth * mZoomScrollLimit < width) {
+ halfX = zoomFrameScaleX(width, 0.5f, halfX);
+ }
+ float halfY = (float) mZoomScrollY / maxY;
+ if (mContentHeight * mZoomScrollLimit < height) {
+ halfY = zoomFrameScaleY(height, 0.5f, halfY);
+ }
canvas.scale(halfScale, halfScale, mZoomScrollX + width * halfX
, mZoomScrollY + height * halfY);
if (LOGV_ENABLED) {
@@ -2350,7 +2497,10 @@ public class WebView extends AbsoluteLayout
}
private void zoomScrollOut() {
- if (canZoomScrollOut() == false) return;
+ if (canZoomScrollOut() == false) {
+ mTouchMode = TOUCH_DONE_MODE;
+ return;
+ }
startZoomScrollOut();
mTouchMode = SCROLL_ZOOM_ANIMATION_OUT;
invalidate();
@@ -2377,8 +2527,8 @@ public class WebView extends AbsoluteLayout
+ " mZoomScrollLimit=" + mZoomScrollLimit + " x=" + x);
}
x += maxScreenX * mLastScrollX / maxZoomX - mLastTouchX;
- x = Math.max(0, Math.min(maxScreenX, x));
- mZoomScrollX = (int) (x * maxZoomX / maxScreenX);
+ x *= Math.max(maxZoomX / maxScreenX, mZoomScrollInvLimit);
+ mZoomScrollX = Math.max(0, Math.min(maxZoomX, (int) x));
}
int maxZoomY = mContentHeight - height;
if (maxZoomY > 0) {
@@ -2390,8 +2540,8 @@ public class WebView extends AbsoluteLayout
+ " mZoomScrollLimit=" + mZoomScrollLimit + " y=" + y);
}
y += maxScreenY * mLastScrollY / maxZoomY - mLastTouchY;
- y = Math.max(0, Math.min(maxScreenY, y));
- mZoomScrollY = (int) (y * maxZoomY / maxScreenY);
+ y *= Math.max(maxZoomY / maxScreenY, mZoomScrollInvLimit);
+ mZoomScrollY = Math.max(0, Math.min(maxZoomY, (int) y));
}
if (oldX != mZoomScrollX || oldY != mZoomScrollY) {
invalidate();
@@ -2540,6 +2690,13 @@ public class WebView extends AbsoluteLayout
new WebViewCore.FocusData(mFocusData));
}
+ // Called by JNI when a touch event puts a textfield into focus.
+ private void displaySoftKeyboard() {
+ InputMethodManager imm = (InputMethodManager)
+ getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.showSoftInput(mTextEntry);
+ }
+
// Used to register the global focus change listener one time to avoid
// multiple references to WebView
private boolean mGlobalFocusChangeListenerAdded;
@@ -2663,7 +2820,8 @@ public class WebView extends AbsoluteLayout
ArrayList<String> pastEntries = mDatabase.getFormData(mUrl, mName);
if (pastEntries.size() > 0) {
ArrayAdapter<String> adapter = new ArrayAdapter<String>(
- mContext, com.android.internal.R.layout.simple_list_item_1,
+ mContext, com.android.internal.R.layout
+ .search_dropdown_item_1line,
pastEntries);
((HashMap) mUpdateMessage.obj).put("adapter", adapter);
mUpdateMessage.sendToTarget();
@@ -2679,152 +2837,166 @@ public class WebView extends AbsoluteLayout
mTextEntry.setRect(x, y, width, height);
}
- // These variables are used to determine long press with the enter key, or
+ // This is used to determine long press with the enter key, or
// a center key. Does not affect long press with the trackball/touch.
- private long mDownTime = 0;
- private boolean mGotDown = false;
+ private boolean mGotEnterDown = false;
// Enable copy/paste with trackball here.
// This should be left disabled until the framework can guarantee
// delivering matching key-up and key-down events for the shift key
- private static final boolean ENABLE_COPY_PASTE = false;
+ private static final boolean ENABLE_COPY_PASTE = true;
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
- if (mTouchMode >= FIRST_SCROLL_ZOOM && mTouchMode <= LAST_SCROLL_ZOOM) {
+ if (LOGV_ENABLED) {
+ Log.v(LOGTAG, "keyDown at " + System.currentTimeMillis()
+ + ", " + event);
+ }
+
+ if (mNativeClass == 0) {
return false;
}
- if (keyCode != KeyEvent.KEYCODE_DPAD_CENTER &&
- keyCode != KeyEvent.KEYCODE_ENTER) {
- mGotDown = false;
+
+ // do this hack up front, so it always works, regardless of touch-mode
+ if (AUTO_REDRAW_HACK && (keyCode == KeyEvent.KEYCODE_CALL)) {
+ mAutoRedraw = !mAutoRedraw;
+ if (mAutoRedraw) {
+ invalidate();
+ }
+ return true;
}
- if (mAltIsPressed == false && (keyCode == KeyEvent.KEYCODE_ALT_LEFT
- || keyCode == KeyEvent.KEYCODE_ALT_RIGHT)) {
- mAltIsPressed = true;
+
+ // Bubble up the key event if
+ // 1. it is a system key; or
+ // 2. the host application wants to handle it; or
+ // 3. webview is in scroll-zoom state;
+ if (event.isSystem()
+ || mCallbackProxy.uiOverrideKeyEvent(event)
+ || (mTouchMode >= FIRST_SCROLL_ZOOM && mTouchMode <= LAST_SCROLL_ZOOM)) {
+ return false;
}
+
if (ENABLE_COPY_PASTE && mShiftIsPressed == false
&& (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT
|| keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT)) {
mExtendSelection = false;
mShiftIsPressed = true;
- if (mNativeClass != 0 && nativeUpdateFocusNode()) {
+ if (nativeUpdateFocusNode()) {
FocusNode node = mFocusNode;
- mSelectX = node.mBounds.left;
- mSelectY = node.mBounds.top;
+ mSelectX = contentToView(node.mBounds.left);
+ mSelectY = contentToView(node.mBounds.top);
} else {
- mSelectX = mScrollX + SELECT_CURSOR_OFFSET;
- mSelectY = mScrollY + SELECT_CURSOR_OFFSET;
+ mSelectX = mScrollX + (int) mLastTouchX;
+ mSelectY = mScrollY + (int) mLastTouchY;
}
}
- if (keyCode == KeyEvent.KEYCODE_CALL) {
- if (mNativeClass != 0 && nativeUpdateFocusNode()) {
- FocusNode node = mFocusNode;
- String text = node.mText;
- if (!node.mIsTextField && !node.mIsTextArea && text != null &&
- text.startsWith(SCHEME_TEL)) {
- Intent intent = new Intent(Intent.ACTION_DIAL,
- Uri.parse(text));
- getContext().startActivity(intent);
- return true;
- }
+
+ if (keyCode >= KeyEvent.KEYCODE_DPAD_UP
+ && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) {
+ // always handle the navigation keys in the UI thread
+ switchOutDrawHistory();
+ if (navHandledKey(keyCode, 1, false, event.getEventTime())) {
+ playSoundEffect(keyCodeToSoundsEffect(keyCode));
+ return true;
}
+ // Bubble up the key event as WebView doesn't handle it
return false;
}
- if (event.isSystem() || mCallbackProxy.uiOverrideKeyEvent(event)) {
- return false;
- }
- if (LOGV_ENABLED) {
- Log.v(LOGTAG, "keyDown at " + System.currentTimeMillis()
- + ", " + event);
- }
- boolean isArrowKey = keyCode == KeyEvent.KEYCODE_DPAD_UP ||
- keyCode == KeyEvent.KEYCODE_DPAD_DOWN ||
- keyCode == KeyEvent.KEYCODE_DPAD_LEFT ||
- keyCode == KeyEvent.KEYCODE_DPAD_RIGHT;
- if (isArrowKey && event.getEventTime() - mTrackballLastTime
- <= TRACKBALL_KEY_TIMEOUT) {
- if (LOGV_ENABLED) Log.v(LOGTAG, "ignore arrow");
+ if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER
+ || keyCode == KeyEvent.KEYCODE_ENTER) {
+ switchOutDrawHistory();
+ if (event.getRepeatCount() == 0) {
+ mGotEnterDown = true;
+ mPrivateHandler.sendMessageDelayed(mPrivateHandler
+ .obtainMessage(LONG_PRESS_ENTER), LONG_PRESS_TIMEOUT);
+ nativeRecordButtons(true, true);
+ // FIXME, currently in webcore keydown it doesn't do anything.
+ // In keyup, it calls both keydown and keyup, we should fix it.
+ mWebViewCore.sendMessage(EventHub.KEY_DOWN, keyCode,
+ EventHub.KEYEVENT_UNHANDLED_TYPE, event);
+ return true;
+ }
+ // Bubble up the key event as WebView doesn't handle it
return false;
}
- boolean weHandledTheKey = false;
- if (event.getMetaState() == 0) {
+ if (getSettings().getNavDump()) {
switch (keyCode) {
- case KeyEvent.KEYCODE_DPAD_UP:
- case KeyEvent.KEYCODE_DPAD_DOWN:
- case KeyEvent.KEYCODE_DPAD_RIGHT:
- case KeyEvent.KEYCODE_DPAD_LEFT:
- // TODO: alternatively we can do panning as touch does
- switchOutDrawHistory();
- weHandledTheKey = navHandledKey(keyCode, 1, false
- , event.getEventTime());
- if (weHandledTheKey) {
- playSoundEffect(keyCodeToSoundsEffect(keyCode));
- }
+ case KeyEvent.KEYCODE_4:
+ // "/data/data/com.android.browser/displayTree.txt"
+ nativeDumpDisplayTree(getUrl());
break;
- case KeyEvent.KEYCODE_DPAD_CENTER:
- case KeyEvent.KEYCODE_ENTER:
- if (event.getRepeatCount() == 0) {
- switchOutDrawHistory();
- mDownTime = event.getEventTime();
- mGotDown = true;
- return true;
- }
- if (mGotDown && event.getEventTime() - mDownTime >
- ViewConfiguration.getLongPressTimeout()) {
- performLongClick();
- mGotDown = false;
- return true;
- }
+ case KeyEvent.KEYCODE_5:
+ case KeyEvent.KEYCODE_6:
+ // 5: dump the dom tree to the file
+ // "/data/data/com.android.browser/domTree.txt"
+ // 6: dump the dom tree to the adb log
+ mWebViewCore.sendMessage(EventHub.DUMP_DOMTREE,
+ (keyCode == KeyEvent.KEYCODE_5) ? 1 : 0, 0);
break;
- case KeyEvent.KEYCODE_9:
- if (mNativeClass != 0 && getSettings().getNavDump()) {
- debugDump();
- }
+ case KeyEvent.KEYCODE_7:
+ case KeyEvent.KEYCODE_8:
+ // 7: dump the render tree to the file
+ // "/data/data/com.android.browser/renderTree.txt"
+ // 8: dump the render tree to the adb log
+ mWebViewCore.sendMessage(EventHub.DUMP_RENDERTREE,
+ (keyCode == KeyEvent.KEYCODE_7) ? 1 : 0, 0);
break;
- default:
+ case KeyEvent.KEYCODE_9:
+ debugDump();
break;
}
}
- // suppress sending arrow keys to webkit
- if (!weHandledTheKey && !isArrowKey) {
- mWebViewCore.sendMessage(EventHub.KEY_DOWN, keyCode, 0, event);
+ if (nativeFocusNodeWantsKeyEvents()) {
+ mWebViewCore.sendMessage(EventHub.KEY_DOWN, keyCode,
+ EventHub.KEYEVENT_FOCUS_NODE_TYPE, event);
+ // return true as DOM handles the key
+ return true;
+ } else if (false) { // reserved to check the meta tag
+ // pass the key to DOM
+ mWebViewCore.sendMessage(EventHub.KEY_DOWN, keyCode,
+ EventHub.KEYEVENT_UNHANDLED_TYPE, event);
+ // return true as DOM handles the key
+ return true;
}
- return weHandledTheKey;
+ // Bubble up the key event as WebView doesn't handle it
+ return false;
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT
- || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
- if (mExtendSelection) {
- // copy region so core operates on copy without touching orig.
- Region selection = new Region(nativeGetSelection());
- if (selection.isEmpty() == false) {
- Toast.makeText(mContext
- , com.android.internal.R.string.text_copied
- , Toast.LENGTH_SHORT).show();
- mWebViewCore.sendMessage(EventHub.GET_SELECTION, selection);
- }
- }
- mShiftIsPressed = false;
- return true;
- }
if (LOGV_ENABLED) {
- Log.v(LOGTAG, "MT keyUp at" + System.currentTimeMillis()
+ Log.v(LOGTAG, "keyUp at " + System.currentTimeMillis()
+ ", " + event);
}
- if (keyCode == KeyEvent.KEYCODE_ALT_LEFT
- || keyCode == KeyEvent.KEYCODE_ALT_RIGHT) {
- mAltIsPressed = false;
+
+ if (mNativeClass == 0) {
+ return false;
}
+
+ // special CALL handling when focus node's href is "tel:XXX"
+ if (keyCode == KeyEvent.KEYCODE_CALL && nativeUpdateFocusNode()) {
+ FocusNode node = mFocusNode;
+ String text = node.mText;
+ if (!node.mIsTextField && !node.mIsTextArea && text != null
+ && text.startsWith(SCHEME_TEL)) {
+ Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse(text));
+ getContext().startActivity(intent);
+ return true;
+ }
+ }
+
+ // Bubble up the key event if
+ // 1. it is a system key; or
+ // 2. the host application wants to handle it;
if (event.isSystem() || mCallbackProxy.uiOverrideKeyEvent(event)) {
return false;
}
+ // special handling in scroll_zoom state
if (mTouchMode >= FIRST_SCROLL_ZOOM && mTouchMode <= LAST_SCROLL_ZOOM) {
if (KeyEvent.KEYCODE_DPAD_CENTER == keyCode
&& mTouchMode != SCROLL_ZOOM_ANIMATION_IN) {
@@ -2835,64 +3007,112 @@ public class WebView extends AbsoluteLayout
}
return false;
}
- Rect visibleRect = sendOurVisibleRect();
- // Note that sendOurVisibleRect calls viewToContent, so the coordinates
- // should be in content coordinates.
- boolean nodeOnScreen = false;
- boolean isTextField = false;
- boolean isTextArea = false;
- FocusNode node = null;
- if (mNativeClass != 0 && nativeUpdateFocusNode()) {
- node = mFocusNode;
- isTextField = node.mIsTextField;
- isTextArea = node.mIsTextArea;
- nodeOnScreen = Rect.intersects(node.mBounds, visibleRect);
- }
-
- if (KeyEvent.KEYCODE_DPAD_CENTER == keyCode) {
- if (mShiftIsPressed) {
- return false;
- }
- if (getSettings().supportZoom()) {
- if (mTouchMode == TOUCH_DOUBLECLICK_MODE) {
- zoomScrollOut();
- } else {
- if (LOGV_ENABLED) Log.v(LOGTAG, "TOUCH_DOUBLECLICK_MODE");
- mPrivateHandler.sendMessageDelayed(mPrivateHandler
- .obtainMessage(SWITCH_TO_ENTER), TAP_TIMEOUT);
- mTouchMode = TOUCH_DOUBLECLICK_MODE;
- }
+
+ if (ENABLE_COPY_PASTE && (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT
+ || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT)) {
+ if (commitCopy()) {
return true;
- } else {
- keyCode = KeyEvent.KEYCODE_ENTER;
}
}
- if (KeyEvent.KEYCODE_ENTER == keyCode) {
- if (LOGV_ENABLED) Log.v(LOGTAG, "KEYCODE_ENTER == keyCode");
- if (!nodeOnScreen) {
- return false;
+
+ if (keyCode >= KeyEvent.KEYCODE_DPAD_UP
+ && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) {
+ // always handle the navigation keys in the UI thread
+ // Bubble up the key event as WebView doesn't handle it
+ return false;
+ }
+
+ if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER
+ || keyCode == KeyEvent.KEYCODE_ENTER) {
+ // remove the long press message first
+ mPrivateHandler.removeMessages(LONG_PRESS_ENTER);
+ mGotEnterDown = false;
+
+ if (KeyEvent.KEYCODE_DPAD_CENTER == keyCode) {
+ if (mShiftIsPressed) {
+ return false;
+ }
+ if (getSettings().supportZoom()) {
+ if (mTouchMode == TOUCH_DOUBLECLICK_MODE) {
+ zoomScrollOut();
+ } else {
+ if (LOGV_ENABLED) {
+ Log.v(LOGTAG, "TOUCH_DOUBLECLICK_MODE");
+ }
+ mPrivateHandler.sendMessageDelayed(mPrivateHandler
+ .obtainMessage(SWITCH_TO_ENTER), TAP_TIMEOUT);
+ mTouchMode = TOUCH_DOUBLECLICK_MODE;
+ }
+ return true;
+ }
+ }
+
+ Rect visibleRect = sendOurVisibleRect();
+ // Note that sendOurVisibleRect calls viewToContent, so the
+ // coordinates should be in content coordinates.
+ boolean nodeOnScreen = false;
+ boolean isTextField = false;
+ boolean isTextArea = false;
+ FocusNode node = null;
+ if (nativeUpdateFocusNode()) {
+ node = mFocusNode;
+ isTextField = node.mIsTextField;
+ isTextArea = node.mIsTextArea;
+ nodeOnScreen = Rect.intersects(node.mBounds, visibleRect);
}
- if (node != null && !isTextField && !isTextArea) {
+ if (nodeOnScreen && !isTextField && !isTextArea) {
nativeSetFollowedLink(true);
- mWebViewCore.sendMessage(EventHub.SET_FINAL_FOCUS,
+ mWebViewCore.sendMessage(EventHub.SET_FINAL_FOCUS,
EventHub.BLOCK_FOCUS_CHANGE_UNTIL_KEY_UP, 0,
new WebViewCore.FocusData(mFocusData));
- if (mCallbackProxy.uiOverrideUrlLoading(node.mText)) {
- return true;
- }
playSoundEffect(SoundEffectConstants.CLICK);
+ if (!mCallbackProxy.uiOverrideUrlLoading(node.mText)) {
+ mWebViewCore.sendMessage(EventHub.KEY_UP, keyCode,
+ EventHub.KEYEVENT_UNHANDLED_TYPE, event);
+ }
+ return true;
}
- }
- if (!nodeOnScreen) {
- // FIXME: Want to give Callback a chance to handle it, and
- // possibly pass down to javascript.
+ // Bubble up the key event as WebView doesn't handle it
return false;
}
- if (LOGV_ENABLED) Log.v(LOGTAG, "onKeyUp send EventHub.KEY_UP");
- mWebViewCore.sendMessage(EventHub.KEY_UP, keyCode, 0, event);
- return true;
+
+ if (nativeFocusNodeWantsKeyEvents()) {
+ mWebViewCore.sendMessage(EventHub.KEY_UP, keyCode,
+ EventHub.KEYEVENT_FOCUS_NODE_TYPE, event);
+ // return true as DOM handles the key
+ return true;
+ } else if (false) { // reserved to check the meta tag
+ // pass the key to DOM
+ mWebViewCore.sendMessage(EventHub.KEY_UP, keyCode,
+ EventHub.KEYEVENT_UNHANDLED_TYPE, event);
+ // return true as DOM handles the key
+ return true;
+ }
+
+ // Bubble up the key event as WebView doesn't handle it
+ return false;
}
-
+
+ private boolean commitCopy() {
+ boolean copiedSomething = false;
+ if (mExtendSelection) {
+ // copy region so core operates on copy without touching orig.
+ Region selection = new Region(nativeGetSelection());
+ if (selection.isEmpty() == false) {
+ Toast.makeText(mContext
+ , com.android.internal.R.string.text_copied
+ , Toast.LENGTH_SHORT).show();
+ mWebViewCore.sendMessage(EventHub.GET_SELECTION, selection);
+ copiedSomething = true;
+ }
+ }
+ mShiftIsPressed = false;
+ if (mTouchMode == TOUCH_SELECT_MODE) {
+ mTouchMode = TOUCH_INIT_MODE;
+ }
+ return copiedSomething;
+ }
+
// Set this as a hierarchy change listener so we can know when this view
// is removed and still have access to our parent.
@Override
@@ -2962,6 +3182,7 @@ public class WebView extends AbsoluteLayout
// we regain focus.
mDrawFocusRing = false;
mGotKeyDown = false;
+ mShiftIsPressed = false;
if (inEditingMode()) {
clearTextEntry();
mNeedsUpdateTextEntry = true;
@@ -3055,8 +3276,7 @@ public class WebView extends AbsoluteLayout
@Override
public boolean onTouchEvent(MotionEvent ev) {
- if (mNativeClass == 0 || !isClickable() || !isLongClickable() ||
- !hasFocus()) {
+ if (mNativeClass == 0 || !isClickable() || !isLongClickable()) {
return false;
}
@@ -3069,6 +3289,32 @@ public class WebView extends AbsoluteLayout
float x = ev.getX();
float y = ev.getY();
long eventTime = ev.getEventTime();
+
+ // Due to the touch screen edge effect, a touch closer to the edge
+ // always snapped to the edge. As getViewWidth() can be different from
+ // getWidth() due to the scrollbar, adjusting the point to match
+ // getViewWidth(). Same applied to the height.
+ if (x > getViewWidth() - 1) {
+ x = getViewWidth() - 1;
+ }
+ if (y > getViewHeight() - 1) {
+ y = getViewHeight() - 1;
+ }
+
+ // pass the touch events from UI thread to WebCore thread
+ if (mForwardTouchEvents && mTouchMode != SCROLL_ZOOM_OUT
+ && mTouchMode != SCROLL_ZOOM_ANIMATION_IN
+ && mTouchMode != SCROLL_ZOOM_ANIMATION_OUT
+ && (action != MotionEvent.ACTION_MOVE ||
+ eventTime - mLastSentTouchTime > TOUCH_SENT_INTERVAL)) {
+ WebViewCore.TouchEventData ted = new WebViewCore.TouchEventData();
+ ted.mAction = action;
+ ted.mX = viewToContent((int) x + mScrollX);
+ ted.mY = viewToContent((int) y + mScrollY);;
+ mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
+ mLastSentTouchTime = eventTime;
+ }
+
switch (action) {
case MotionEvent.ACTION_DOWN: {
if (mTouchMode == SCROLL_ZOOM_ANIMATION_IN
@@ -3083,6 +3329,16 @@ public class WebView extends AbsoluteLayout
mScroller.abortAnimation();
mTouchMode = TOUCH_DRAG_START_MODE;
mPrivateHandler.removeMessages(RESUME_WEBCORE_UPDATE);
+ } else if (mShiftIsPressed) {
+ mSelectX = mScrollX + (int) x;
+ mSelectY = mScrollY + (int) y;
+ mTouchMode = TOUCH_SELECT_MODE;
+ if (LOGV_ENABLED) {
+ Log.v(LOGTAG, "select=" + mSelectX + "," + mSelectY);
+ }
+ nativeMoveSelection(viewToContent(mSelectX)
+ , viewToContent(mSelectY), false);
+ mTouchSelection = mExtendSelection = true;
} else {
mTouchMode = TOUCH_INIT_MODE;
}
@@ -3116,6 +3372,17 @@ public class WebView extends AbsoluteLayout
int deltaY = (int) (mLastTouchY - y);
if (mTouchMode != TOUCH_DRAG_MODE) {
+ if (mTouchMode == TOUCH_SELECT_MODE) {
+ mSelectX = mScrollX + (int) x;
+ mSelectY = mScrollY + (int) y;
+ if (LOGV_ENABLED) {
+ Log.v(LOGTAG, "xtend=" + mSelectX + "," + mSelectY);
+ }
+ nativeMoveSelection(viewToContent(mSelectX)
+ , viewToContent(mSelectY), true);
+ invalidate();
+ break;
+ }
if ((deltaX * deltaX + deltaY * deltaY)
< TOUCH_SLOP_SQUARE) {
break;
@@ -3214,11 +3481,13 @@ public class WebView extends AbsoluteLayout
mUserScroll = true;
}
- if (mZoomControls != null && mMinZoomScale < mMaxZoomScale) {
+ boolean showPlusMinus = mMinZoomScale < mMaxZoomScale;
+ boolean showMagnify = canZoomScrollOut();
+ if (mZoomControls != null && (showPlusMinus || showMagnify)) {
if (mZoomControls.getVisibility() == View.VISIBLE) {
mPrivateHandler.removeCallbacks(mZoomControlRunnable);
} else {
- mZoomControls.show(canZoomScrollOut());
+ mZoomControls.show(showPlusMinus, showMagnify);
}
mPrivateHandler.postDelayed(mZoomControlRunnable,
ZOOM_CONTROLS_TIMEOUT);
@@ -3244,6 +3513,10 @@ public class WebView extends AbsoluteLayout
doShortPress();
}
break;
+ case TOUCH_SELECT_MODE:
+ commitCopy();
+ mTouchSelection = mExtendSelection = false;
+ break;
case SCROLL_ZOOM_ANIMATION_IN:
case SCROLL_ZOOM_ANIMATION_OUT:
// no action during scroll animation
@@ -3349,6 +3622,7 @@ public class WebView extends AbsoluteLayout
private int mTrackballXMove = 0;
private int mTrackballYMove = 0;
private boolean mExtendSelection = false;
+ private boolean mTouchSelection = false;
private static final int TRACKBALL_KEY_TIMEOUT = 1000;
private static final int TRACKBALL_TIMEOUT = 200;
private static final int TRACKBALL_WAIT = 100;
@@ -3359,7 +3633,6 @@ public class WebView extends AbsoluteLayout
private int mSelectX = 0;
private int mSelectY = 0;
private boolean mShiftIsPressed = false;
- private boolean mAltIsPressed = false;
private boolean mTrackballDown = false;
private long mTrackballUpTime = 0;
private long mLastFocusTime = 0;
@@ -3386,17 +3659,18 @@ public class WebView extends AbsoluteLayout
@Override
public boolean onTrackballEvent(MotionEvent ev) {
long time = ev.getEventTime();
- if (mAltIsPressed) {
+ if ((ev.getMetaState() & KeyEvent.META_ALT_ON) != 0) {
if (ev.getY() > 0) pageDown(true);
if (ev.getY() < 0) pageUp(true);
return true;
}
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
mPrivateHandler.removeMessages(SWITCH_TO_ENTER);
- mPrivateHandler.sendMessageDelayed(
- mPrivateHandler.obtainMessage(LONG_PRESS_TRACKBALL), 1000);
mTrackTrackball = true;
mTrackballDown = true;
+ if (mNativeClass != 0) {
+ nativeRecordButtons(true, true);
+ }
if (time - mLastFocusTime <= TRACKBALL_TIMEOUT
&& !mLastFocusBounds.equals(nativeGetFocusRingBounds())) {
nativeSelectBestAt(mLastFocusBounds);
@@ -3408,7 +3682,8 @@ public class WebView extends AbsoluteLayout
}
return false; // let common code in onKeyDown at it
} else if (mTrackTrackball) {
- mPrivateHandler.removeMessages(LONG_PRESS_TRACKBALL);
+ // LONG_PRESS_ENTER is set in common onKeyDown
+ mPrivateHandler.removeMessages(LONG_PRESS_ENTER);
mTrackTrackball = false;
}
if (ev.getAction() == MotionEvent.ACTION_UP) {
@@ -3823,13 +4098,7 @@ public class WebView extends AbsoluteLayout
return;
}
switchOutDrawHistory();
- // mLastTouchX and mLastTouchY are the point in the current viewport
- int contentX = viewToContent((int) mLastTouchX + mScrollX);
- int contentY = viewToContent((int) mLastTouchY + mScrollY);
- int contentSize = ViewConfiguration.getTouchSlop();
- nativeMotionUp(contentX, contentY, contentSize, true);
-
- // call uiOverride next to check whether it is a special node,
+ // call uiOverride to check whether it is a special node,
// phone/email/address, which are not handled by WebKit
if (nativeUpdateFocusNode()) {
FocusNode node = mFocusNode;
@@ -3840,6 +4109,11 @@ public class WebView extends AbsoluteLayout
}
playSoundEffect(SoundEffectConstants.CLICK);
}
+ // mLastTouchX and mLastTouchY are the point in the current viewport
+ int contentX = viewToContent((int) mLastTouchX + mScrollX);
+ int contentY = viewToContent((int) mLastTouchY + mScrollY);
+ int contentSize = ViewConfiguration.getTouchSlop();
+ nativeMotionUp(contentX, contentY, contentSize, true);
}
@Override
@@ -3907,6 +4181,8 @@ public class WebView extends AbsoluteLayout
mHeightCanMeasure = false;
}
}
+ } else {
+ mHeightCanMeasure = false;
}
if (mNativeClass != 0) {
nativeSetHeightCanMeasure(mHeightCanMeasure);
@@ -3915,6 +4191,8 @@ public class WebView extends AbsoluteLayout
if (widthMode == MeasureSpec.UNSPECIFIED) {
mWidthCanMeasure = true;
measuredWidth = contentWidth;
+ } else {
+ mWidthCanMeasure = false;
}
synchronized (this) {
@@ -4076,10 +4354,14 @@ public class WebView extends AbsoluteLayout
break;
case NEW_PICTURE_MSG_ID:
// called for new content
- final Point viewSize = (Point) msg.obj;
+ final WebViewCore.DrawData draw =
+ (WebViewCore.DrawData) msg.obj;
+ final Point viewSize = draw.mViewPoint;
if (mZoomScale > 0) {
- if (Math.abs(mZoomScale * viewSize.x -
- getViewWidth()) < 1) {
+ // use the same logic in sendViewSizeZoom() to make sure
+ // the mZoomScale has matched the viewSize so that we
+ // can clear mZoomScale
+ if (Math.round(getViewWidth() / mZoomScale) == viewSize.x) {
mZoomScale = 0;
mWebViewCore.sendMessage(EventHub.SET_SNAP_ANCHOR,
0, 0);
@@ -4091,8 +4373,14 @@ public class WebView extends AbsoluteLayout
// received in the fixed dimension.
final boolean updateLayout = viewSize.x == mLastWidthSent
&& viewSize.y == mLastHeightSent;
- recordNewContentSize(msg.arg1, msg.arg2, updateLayout);
- invalidate();
+ recordNewContentSize(draw.mWidthHeight.x,
+ draw.mWidthHeight.y, updateLayout);
+ if (LOGV_ENABLED) {
+ Rect b = draw.mInvalRegion.getBounds();
+ Log.v(LOGTAG, "NEW_PICTURE_MSG_ID {" +
+ b.left+","+b.top+","+b.right+","+b.bottom+"}");
+ }
+ invalidate(contentToView(draw.mInvalRegion.getBounds()));
if (mPictureListener != null) {
mPictureListener.onNewPicture(WebView.this, capturePicture());
}
@@ -4156,6 +4444,7 @@ public class WebView extends AbsoluteLayout
}
int initialScale = msg.arg1;
int viewportWidth = msg.arg2;
+ // by default starting a new page with 100% zoom scale.
float scale = 1.0f;
if (mInitialScale > 0) {
scale = mInitialScale / 100.0f;
@@ -4165,10 +4454,15 @@ public class WebView extends AbsoluteLayout
// to 0
mLastWidthSent = 0;
}
- // by default starting a new page with 100% zoom scale.
- scale = initialScale == 0 ? (viewportWidth > 0 ?
- ((float) width / viewportWidth) : 1.0f)
- : initialScale / 100.0f;
+ if (initialScale == 0) {
+ // if viewportWidth is defined and it is smaller
+ // than the view width, zoom in to fill the view
+ if (viewportWidth > 0 && viewportWidth < width) {
+ scale = (float) width / viewportWidth;
+ }
+ } else {
+ scale = initialScale / 100.0f;
+ }
}
setNewZoomScale(scale, false);
break;
@@ -4213,10 +4507,32 @@ public class WebView extends AbsoluteLayout
WebViewCore.resumeUpdate(mWebViewCore);
break;
- case LONG_PRESS_TRACKBALL:
+ case LONG_PRESS_ENTER:
+ // as this is shared by keydown and trackballdown, reset all
+ // the states
+ mGotEnterDown = false;
mTrackTrackball = false;
mTrackballDown = false;
- performLongClick();
+ // LONG_PRESS_ENTER is sent as a delayed message. If we
+ // switch to windows overview, the WebView will be
+ // temporarily removed from the view system. In that case,
+ // do nothing.
+ if (getParent() != null) {
+ performLongClick();
+ }
+ break;
+
+ case WEBCORE_NEED_TOUCH_EVENTS:
+ mForwardTouchEvents = (msg.arg1 != 0);
+ break;
+
+ case PREVENT_TOUCH_ID:
+ // update may have already been paused by touch; restore since
+ // this effectively aborts touch and skips logic in touch up
+ if (mTouchMode == TOUCH_DRAG_MODE) {
+ WebViewCore.resumeUpdate(mWebViewCore);
+ }
+ mTouchMode = TOUCH_DONE_MODE;
break;
default:
@@ -4514,10 +4830,7 @@ public class WebView extends AbsoluteLayout
}
Rect contentFocus = nativeGetFocusRingBounds();
if (contentFocus.isEmpty()) return keyHandled;
- Rect viewFocus = new Rect(contentToView(contentFocus.left)
- , contentToView(contentFocus.top)
- , contentToView(contentFocus.right)
- , contentToView(contentFocus.bottom));
+ Rect viewFocus = contentToView(contentFocus);
Rect visRect = new Rect();
calcOurVisibleRect(visRect);
Rect outset = new Rect(visRect);
@@ -4559,7 +4872,7 @@ public class WebView extends AbsoluteLayout
public void debugDump() {
nativeDebugDump();
- mWebViewCore.sendMessage(EventHub.DUMP_WEBKIT);
+ mWebViewCore.sendMessage(EventHub.DUMP_NAVTREE);
}
/**
@@ -4583,6 +4896,7 @@ public class WebView extends AbsoluteLayout
private native void nativeDrawFocusRing(Canvas content);
private native void nativeDrawSelection(Canvas content
, int x, int y, boolean extendSelection);
+ private native void nativeDrawSelectionRegion(Canvas content);
private native boolean nativeUpdateFocusNode();
private native Rect nativeGetFocusRingBounds();
private native Rect nativeGetNavBounds();
@@ -4593,17 +4907,27 @@ public class WebView extends AbsoluteLayout
boolean noScroll);
private native void nativeNotifyFocusSet(boolean inEditingMode);
private native void nativeRecomputeFocus();
- private native void nativeRecordButtons();
+ // Like many other of our native methods, you must make sure that
+ // mNativeClass is not null before calling this method.
+ private native void nativeRecordButtons(boolean pressed,
+ boolean invalidate);
private native void nativeResetFocus();
private native void nativeResetNavClipBounds();
private native void nativeSelectBestAt(Rect rect);
+ private native void nativeSetFindIsDown();
private native void nativeSetFollowedLink(boolean followed);
private native void nativeSetHeightCanMeasure(boolean measure);
private native void nativeSetNavBounds(Rect rect);
private native void nativeSetNavClipBounds(Rect rect);
- private native boolean nativeHasSrcUrl();
- private native boolean nativeIsImage(int x, int y);
+ private native String nativeImageURI(int x, int y);
+ /**
+ * Returns true if the native focus nodes says it wants to handle key events
+ * (ala plugins). This can only be called if mNativeClass is non-zero!
+ */
+ private native boolean nativeFocusNodeWantsKeyEvents();
private native void nativeMoveSelection(int x, int y
, boolean extendSelection);
private native Region nativeGetSelection();
+
+ private native void nativeDumpDisplayTree(String urlOrNull);
}
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 263346e..9e413f9 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -70,13 +70,6 @@ final class WebViewCore {
// The BrowserFrame is an interface to the native Frame component.
private BrowserFrame mBrowserFrame;
-
- /* This is a ring of pictures for content. After B is built, it is swapped
- with A.
- */
- private Picture mContentPictureA = new Picture(); // draw()
- private Picture mContentPictureB = new Picture(); // nativeDraw()
-
/*
* range is from 200 to 10,000. 0 is a special value means device-width. -1
* means undefined.
@@ -196,7 +189,8 @@ final class WebViewCore {
sWebCoreHandler.removeMessages(WebCoreThread.INITIALIZE, this);
}
- /* Get the BrowserFrame component. This is used for subwindow creation. */
+ /* Get the BrowserFrame component. This is used for subwindow creation and
+ * is called only from BrowserFrame in the WebCore thread. */
/* package */ BrowserFrame getBrowserFrame() {
return mBrowserFrame;
}
@@ -278,30 +272,40 @@ final class WebViewCore {
static native String nativeFindAddress(String addr);
/**
- * Find and highlight an occurance of text matching find
- * @param find The text to find.
- * @param forward If true, search forward. Else, search backwards.
- * @param fromSelection Whether to start from the current selection or from
- * the beginning of the viewable page.
- * @return boolean Whether the text was found.
+ * Empty the picture set.
*/
- private native boolean nativeFind(String find,
- boolean forward,
- boolean fromSelection);
-
+ private native void nativeClearContent();
+
+ /**
+ * Create a flat picture from the set of pictures.
+ */
+ private native void nativeCopyContentToPicture(Picture picture);
+
+ /**
+ * Draw the picture set with a background color. Returns true
+ * if some individual picture took too long to draw and can be
+ * split into parts. Called from the UI thread.
+ */
+ private native boolean nativeDrawContent(Canvas canvas, int color);
+
/**
- * Find all occurances of text matching find and highlight them.
- * @param find The text to find.
- * @return int The number of occurances of find found.
+ * Redraw a portion of the picture set. The Point wh returns the
+ * width and height of the overall picture.
*/
- private native int nativeFindAll(String find);
+ private native boolean nativeRecordContent(Region invalRegion, Point wh);
/**
- * Clear highlights on text created by nativeFindAll.
+ * Splits slow parts of the picture set. Called from the webkit
+ * thread after nativeDrawContent returns true.
*/
- private native void nativeClearMatches();
+ private native void nativeSplitContent();
+
+ // these must be kept lock-step with the KeyState enum in WebViewCore.h
+ static private final int KEY_ACTION_DOWN = 0;
+ static private final int KEY_ACTION_UP = 1;
- private native void nativeDraw(Picture content);
+ private native boolean nativeSendKeyToFocusNode(int keyCode, int unichar,
+ int repeatCount, boolean isShift, boolean isAlt, int keyAction);
private native boolean nativeKeyUp(int keycode, int keyvalue);
@@ -343,21 +347,12 @@ final class WebViewCore {
private native String nativeRetrieveHref(int framePtr, int nodePtr);
- /**
- * Return the url of the image located at (x,y) in content coordinates, or
- * null if there is no image at that point.
- *
- * @param x x content ordinate
- * @param y y content ordinate
- * @return String url of the image located at (x,y), or null if there is
- * no image there.
- */
- private native String nativeRetrieveImageRef(int x, int y);
-
private native void nativeTouchUp(int touchGeneration,
int buildGeneration, int framePtr, int nodePtr, int x, int y,
int size, boolean isClick, boolean retry);
+ private native boolean nativeHandleTouchEvent(int action, int x, int y);
+
private native void nativeUnblockFocus();
private native void nativeUpdateFrameCache();
@@ -368,7 +363,11 @@ final class WebViewCore {
private native void nativeSetBackgroundColor(int color);
- private native void nativeDump();
+ private native void nativeDumpDomTree(boolean useFile);
+
+ private native void nativeDumpRenderTree(boolean useFile);
+
+ private native void nativeDumpNavTree();
private native void nativeRefreshPlugins(boolean reloadOpenPages);
@@ -393,6 +392,10 @@ final class WebViewCore {
private native String nativeGetSelection(Region sel);
+ // Register a scheme to be treated as local scheme so that it can access
+ // local asset files for resources
+ private native void nativeRegisterURLSchemeAsLocal(String scheme);
+
// EventHub for processing messages
private final EventHub mEventHub;
// WebCore thread handler
@@ -421,10 +424,7 @@ final class WebViewCore {
switch (msg.what) {
case INITIALIZE:
WebViewCore core = (WebViewCore) msg.obj;
- synchronized (core) {
- core.initialize();
- core.notify();
- }
+ core.initialize();
break;
case REDUCE_PRIORITY:
@@ -501,6 +501,12 @@ final class WebViewCore {
boolean mRetry;
}
+ static class TouchEventData {
+ int mAction; // MotionEvent.getAction()
+ int mX;
+ int mY;
+ }
+
class EventHub {
// Message Ids
static final int LOAD_URL = 100;
@@ -510,7 +516,7 @@ final class WebViewCore {
static final int KEY_UP = 104;
static final int VIEW_SIZE_CHANGED = 105;
static final int GO_BACK_FORWARD = 106;
- static final int SET_VISIBLE_RECT = 107;
+ static final int SET_SCROLL_OFFSET = 107;
static final int RESTORE_STATE = 108;
static final int PAUSE_TIMERS = 109;
static final int RESUME_TIMERS = 110;
@@ -519,16 +525,13 @@ final class WebViewCore {
static final int SET_SELECTION = 113;
static final int REPLACE_TEXT = 114;
static final int PASS_TO_JS = 115;
- static final int FIND = 116;
+ static final int SET_GLOBAL_BOUNDS = 116;
static final int UPDATE_CACHE_AND_TEXT_ENTRY = 117;
- static final int FIND_ALL = 118;
- static final int CLEAR_MATCHES = 119;
static final int DOC_HAS_IMAGES = 120;
static final int SET_SNAP_ANCHOR = 121;
static final int DELETE_SELECTION = 122;
static final int LISTBOX_CHOICES = 123;
static final int SINGLE_LISTBOX_CHOICE = 124;
- static final int DUMP_WEBKIT = 125;
static final int SET_BACKGROUND_COLOR = 126;
static final int UNBLOCK_FOCUS = 127;
static final int SAVE_DOCUMENT_STATE = 128;
@@ -536,9 +539,10 @@ final class WebViewCore {
static final int WEBKIT_DRAW = 130;
static final int SYNC_SCROLL = 131;
static final int REFRESH_PLUGINS = 132;
-
+ static final int SPLIT_PICTURE_SET = 133;
+ static final int CLEAR_CONTENT = 134;
+
// UI nav messages
- static final int REQUEST_IMAGE_HREF = 134;
static final int SET_FINAL_FOCUS = 135;
static final int SET_KIT_FOCUS = 136;
static final int REQUEST_FOCUS_HREF = 137;
@@ -547,6 +551,8 @@ final class WebViewCore {
// motion
static final int TOUCH_UP = 140;
+ // message used to pass UI touch events to WebCore
+ static final int TOUCH_EVENT = 141;
// Network-based messaging
static final int CLEAR_SSL_PREF_TABLE = 150;
@@ -554,6 +560,12 @@ final class WebViewCore {
// Test harness messages
static final int REQUEST_EXT_REPRESENTATION = 160;
static final int REQUEST_DOC_AS_TEXT = 161;
+
+ // debugging
+ static final int DUMP_DOMTREE = 170;
+ static final int DUMP_RENDERTREE = 171;
+ static final int DUMP_NAVTREE = 172;
+
// private message ids
private static final int DESTROY = 200;
@@ -561,6 +573,19 @@ final class WebViewCore {
static final int NO_FOCUS_CHANGE_BLOCK = 0;
static final int BLOCK_FOCUS_CHANGE_UNTIL_KEY_UP = 1;
+ /* The KEY_DOWN and KEY_UP messages pass the keyCode in arg1, and a
+ "type" in arg2. These are the types, and they describe what the
+ circumstances were that prompted the UI thread to send the keyevent
+ to webkit.
+
+ FOCUS_NODE - the currently focused node says it wants key events
+ (e.g. plugins)
+ UNHANDLED - the UI side did not handle the key, so we give webkit
+ a shot at it.
+ */
+ static final int KEYEVENT_FOCUS_NODE_TYPE = 0;
+ static final int KEYEVENT_UNHANDLED_TYPE = 1;
+
// Private handler for WebCore messages.
private Handler mHandler;
// Message queue for containing messages before the WebCore thread is
@@ -607,8 +632,30 @@ final class WebViewCore {
case LOAD_DATA:
HashMap loadParams = (HashMap) msg.obj;
- mBrowserFrame.loadData(
- (String) loadParams.get("baseUrl"),
+ String baseUrl = (String) loadParams.get("baseUrl");
+ if (baseUrl != null) {
+ int i = baseUrl.indexOf(':');
+ if (i > 0) {
+ /*
+ * In 1.0, {@link
+ * WebView#loadDataWithBaseURL} can access
+ * local asset files as long as the data is
+ * valid. In the new WebKit, the restriction
+ * is tightened. To be compatible with 1.0,
+ * we automatically add the scheme of the
+ * baseUrl for local access as long as it is
+ * not http(s)/ftp(s)/about/javascript
+ */
+ String scheme = baseUrl.substring(0, i);
+ if (!scheme.startsWith("http") &&
+ !scheme.startsWith("ftp") &&
+ !scheme.startsWith("about") &&
+ !scheme.startsWith("javascript")) {
+ nativeRegisterURLSchemeAsLocal(scheme);
+ }
+ }
+ }
+ mBrowserFrame.loadData(baseUrl,
(String) loadParams.get("data"),
(String) loadParams.get("mimeType"),
(String) loadParams.get("encoding"),
@@ -622,8 +669,7 @@ final class WebViewCore {
// up with native side
if (mBrowserFrame.committed()
&& !mBrowserFrame.firstLayoutDone()) {
- mBrowserFrame.didFirstLayout(mBrowserFrame
- .currentUrl());
+ mBrowserFrame.didFirstLayout();
}
// Do this after syncing up the layout state.
stopLoading();
@@ -634,11 +680,11 @@ final class WebViewCore {
break;
case KEY_DOWN:
- keyDown(msg.arg1, (KeyEvent) msg.obj);
+ keyDown(msg.arg1, msg.arg2, (KeyEvent) msg.obj);
break;
case KEY_UP:
- keyUp(msg.arg1, (KeyEvent) msg.obj);
+ keyUp(msg.arg1, msg.arg2, (KeyEvent) msg.obj);
break;
case VIEW_SIZE_CHANGED:
@@ -646,12 +692,16 @@ final class WebViewCore {
((Float) msg.obj).floatValue());
break;
- case SET_VISIBLE_RECT:
- Rect r = (Rect) msg.obj;
+ case SET_SCROLL_OFFSET:
// note: these are in document coordinates
// (inv-zoom)
- nativeSetVisibleRect(r.left, r.top, r.width(),
- r.height());
+ nativeSetScrollOffset(msg.arg1, msg.arg2);
+ break;
+
+ case SET_GLOBAL_BOUNDS:
+ Rect r = (Rect) msg.obj;
+ nativeSetGlobalBounds(r.left, r.top, r.width(),
+ r.height());
break;
case GO_BACK_FORWARD:
@@ -745,30 +795,6 @@ final class WebViewCore {
break;
}
- case FIND:
- /* arg1:
- * 1 - Find next
- * -1 - Find previous
- * 0 - Find first
- */
- Message response = (Message) msg.obj;
- boolean find = nativeFind(msg.getData().getString("find"),
- msg.arg1 != -1, msg.arg1 != 0);
- response.arg1 = find ? 1 : 0;
- response.sendToTarget();
- break;
-
- case FIND_ALL:
- int found = nativeFindAll(msg.getData().getString("find"));
- Message resAll = (Message) msg.obj;
- resAll.arg1 = found;
- resAll.sendToTarget();
- break;
-
- case CLEAR_MATCHES:
- nativeClearMatches();
- break;
-
case CLEAR_SSL_PREF_TABLE:
Network.getInstance(mContext)
.clearUserSslPrefTable();
@@ -784,6 +810,17 @@ final class WebViewCore {
touchUpData.mRetry);
break;
+ case TOUCH_EVENT: {
+ TouchEventData ted = (TouchEventData) msg.obj;
+ if (nativeHandleTouchEvent(ted.mAction, ted.mX,
+ ted.mY)) {
+ Message.obtain(mWebView.mPrivateHandler,
+ WebView.PREVENT_TOUCH_ID)
+ .sendToTarget();
+ }
+ break;
+ }
+
case ADD_JS_INTERFACE:
HashMap map = (HashMap) msg.obj;
Object obj = map.get("object");
@@ -826,27 +863,17 @@ final class WebViewCore {
case REQUEST_FOCUS_HREF: {
Message hrefMsg = (Message) msg.obj;
String res = nativeRetrieveHref(msg.arg1, msg.arg2);
- Bundle data = hrefMsg.getData();
- data.putString("url", res);
- hrefMsg.setData(data);
+ hrefMsg.getData().putString("url", res);
hrefMsg.sendToTarget();
break;
}
- case REQUEST_IMAGE_HREF: {
- Message refMsg = (Message) msg.obj;
- String ref =
- nativeRetrieveImageRef(msg.arg1, msg.arg2);
- Bundle data = refMsg.getData();
- data.putString("url", ref);
- refMsg.setData(data);
- refMsg.sendToTarget();
- break;
- }
-
case UPDATE_CACHE_AND_TEXT_ENTRY:
nativeUpdateFrameCache();
- sendViewInvalidate();
+ // FIXME: this should provide a minimal rectangle
+ if (mWebView != null) {
+ mWebView.postInvalidate();
+ }
sendUpdateTextEntry();
break;
@@ -901,9 +928,17 @@ final class WebViewCore {
, WebView.UPDATE_CLIPBOARD, str)
.sendToTarget();
break;
-
- case DUMP_WEBKIT:
- nativeDump();
+
+ case DUMP_DOMTREE:
+ nativeDumpDomTree(msg.arg1 == 1);
+ break;
+
+ case DUMP_RENDERTREE:
+ nativeDumpRenderTree(msg.arg1 == 1);
+ break;
+
+ case DUMP_NAVTREE:
+ nativeDumpNavTree();
break;
case SYNC_SCROLL:
@@ -914,6 +949,18 @@ final class WebViewCore {
case REFRESH_PLUGINS:
nativeRefreshPlugins(msg.arg1 != 0);
break;
+
+ case SPLIT_PICTURE_SET:
+ nativeSplitContent();
+ mSplitPictureIsScheduled = false;
+ break;
+
+ case CLEAR_CONTENT:
+ // Clear the view so that onDraw() will draw nothing
+ // but white background
+ // (See public method WebView.clearView)
+ nativeClearContent();
+ break;
}
}
};
@@ -982,6 +1029,7 @@ final class WebViewCore {
private synchronized void removeMessages() {
// reset mDrawIsScheduled flag as WEBKIT_DRAW may be removed
mDrawIsScheduled = false;
+ mSplitPictureIsScheduled = false;
if (mMessages != null) {
mMessages.clear();
} else {
@@ -1001,7 +1049,7 @@ final class WebViewCore {
// Methods called by host activity (in the same thread)
//-------------------------------------------------------------------------
- public void stopLoading() {
+ void stopLoading() {
if (LOGV_ENABLED) Log.v(LOGTAG, "CORE stopLoading");
if (mBrowserFrame != null) {
mBrowserFrame.stopLoading();
@@ -1082,23 +1130,48 @@ final class WebViewCore {
mBrowserFrame.loadUrl(url);
}
- private void keyDown(int code, KeyEvent event) {
+ private void keyDown(int code, int target, KeyEvent event) {
if (LOGV_ENABLED) {
Log.v(LOGTAG, "CORE keyDown at " + System.currentTimeMillis()
+ ", " + event);
}
+ switch (target) {
+ case EventHub.KEYEVENT_UNHANDLED_TYPE:
+ break;
+ case EventHub.KEYEVENT_FOCUS_NODE_TYPE:
+ if (nativeSendKeyToFocusNode(code, event.getUnicodeChar(),
+ event.getRepeatCount(),
+ event.isShiftPressed(),
+ event.isAltPressed(),
+ KEY_ACTION_DOWN)) {
+ return;
+ }
+ break;
+ }
+ // If we get here, no one handled it, so call our proxy
mCallbackProxy.onUnhandledKeyEvent(event);
}
- private void keyUp(int code, KeyEvent event) {
+ private void keyUp(int code, int target, KeyEvent event) {
if (LOGV_ENABLED) {
Log.v(LOGTAG, "CORE keyUp at " + System.currentTimeMillis()
+ ", " + event);
}
- if (!nativeKeyUp(code, event.getUnicodeChar())) {
- mCallbackProxy.onUnhandledKeyEvent(event);
+ switch (target) {
+ case EventHub.KEYEVENT_UNHANDLED_TYPE:
+ if (!nativeKeyUp(code, event.getUnicodeChar())) {
+ mCallbackProxy.onUnhandledKeyEvent(event);
+ }
+ break;
+ case EventHub.KEYEVENT_FOCUS_NODE_TYPE:
+ nativeSendKeyToFocusNode(code, event.getUnicodeChar(),
+ event.getRepeatCount(),
+ event.isShiftPressed(),
+ event.isAltPressed(),
+ KEY_ACTION_UP);
+ break;
+ }
}
- }
// These values are used to avoid requesting a layout based on old values
private int mCurrentViewWidth = 0;
@@ -1140,8 +1213,9 @@ final class WebViewCore {
mCurrentViewHeight = h;
if (needInvalidate) {
// ensure {@link #webkitDraw} is called as we were blocking in
- // {@link #contentInvalidate} when mCurrentViewWidth is 0
- contentInvalidate();
+ // {@link #contentDraw} when mCurrentViewWidth is 0
+ if (LOGV_ENABLED) Log.v(LOGTAG, "viewSizeChanged");
+ contentDraw();
}
mEventHub.sendMessage(Message.obtain(null,
EventHub.UPDATE_CACHE_AND_TEXT_ENTRY));
@@ -1156,30 +1230,42 @@ final class WebViewCore {
// Used to avoid posting more than one draw message.
private boolean mDrawIsScheduled;
+
+ // Used to avoid posting more than one split picture message.
+ private boolean mSplitPictureIsScheduled;
+
+ // Used to suspend drawing.
+ private boolean mDrawIsPaused;
// Used to end scale+scroll mode, accessed by both threads
boolean mEndScaleZoom = false;
+ public class DrawData {
+ public DrawData() {
+ mInvalRegion = new Region();
+ mWidthHeight = new Point();
+ }
+ public Region mInvalRegion;
+ public Point mViewPoint;
+ public Point mWidthHeight;
+ }
+
private void webkitDraw() {
mDrawIsScheduled = false;
- nativeDraw(mContentPictureB);
- int w;
- int h;
- synchronized (this) {
- Picture temp = mContentPictureB;
- mContentPictureB = mContentPictureA;
- mContentPictureA = temp;
- w = mContentPictureA.getWidth();
- h = mContentPictureA.getHeight();
+ DrawData draw = new DrawData();
+ if (LOGV_ENABLED) Log.v(LOGTAG, "webkitDraw start");
+ if (nativeRecordContent(draw.mInvalRegion, draw.mWidthHeight)
+ == false) {
+ if (LOGV_ENABLED) Log.v(LOGTAG, "webkitDraw abort");
+ return;
}
-
if (mWebView != null) {
// Send the native view size that was used during the most recent
// layout.
+ draw.mViewPoint = new Point(mCurrentViewWidth, mCurrentViewHeight);
+ if (LOGV_ENABLED) Log.v(LOGTAG, "webkitDraw NEW_PICTURE_MSG_ID");
Message.obtain(mWebView.mPrivateHandler,
- WebView.NEW_PICTURE_MSG_ID, w, h,
- new Point(mCurrentViewWidth, mCurrentViewHeight))
- .sendToTarget();
+ WebView.NEW_PICTURE_MSG_ID, draw).sendToTarget();
if (mWebkitScrollX != 0 || mWebkitScrollY != 0) {
// as we have the new picture, try to sync the scroll position
Message.obtain(mWebView.mPrivateHandler,
@@ -1217,37 +1303,18 @@ final class WebViewCore {
df = mScrollFilter;
}
canvas.setDrawFilter(df);
- synchronized (this) {
- Picture picture = mContentPictureA;
- int sc = canvas.save(Canvas.CLIP_SAVE_FLAG);
- Rect clip = new Rect(0, 0, picture.getWidth(), picture.getHeight());
- canvas.clipRect(clip, Region.Op.DIFFERENCE);
- canvas.drawColor(color);
- canvas.restoreToCount(sc);
- // experiment commented out
- // if (TEST_BUCKET) {
- // nativeDrawContentPicture(canvas);
- // } else {
- canvas.drawPicture(picture);
- // }
- }
+ boolean tookTooLong = nativeDrawContent(canvas, color);
canvas.setDrawFilter(null);
- }
-
- /* package */ void clearContentPicture() {
- // experiment commented out
- // if (TEST_BUCKET) {
- // nativeClearContentPicture();
- // }
- synchronized (this) {
- mContentPictureA = new Picture();
+ if (tookTooLong && mSplitPictureIsScheduled == false) {
+ mSplitPictureIsScheduled = true;
+ sendMessage(EventHub.SPLIT_PICTURE_SET);
}
}
/*package*/ Picture copyContentPicture() {
- synchronized (this) {
- return new Picture(mContentPictureA);
- }
+ Picture result = new Picture();
+ nativeCopyContentToPicture(result);
+ return result;
}
static void pauseUpdate(WebViewCore core) {
@@ -1263,7 +1330,7 @@ final class WebViewCore {
// webcore thread priority is still lowered.
if (core != null) {
synchronized (core) {
- core.mDrawIsScheduled = true;
+ core.mDrawIsPaused = true;
core.mEventHub.removeMessages(EventHub.WEBKIT_DRAW);
}
}
@@ -1278,7 +1345,9 @@ final class WebViewCore {
if (core != null) {
synchronized (core) {
core.mDrawIsScheduled = false;
- core.contentInvalidate();
+ core.mDrawIsPaused = false;
+ if (LOGV_ENABLED) Log.v(LOGTAG, "resumeUpdate");
+ core.contentDraw();
}
}
}
@@ -1309,7 +1378,7 @@ final class WebViewCore {
//-------------------------------------------------------------------------
// called from JNI or WebView thread
- /* package */ void contentInvalidate() {
+ /* package */ void contentDraw() {
// don't update the Picture until we have an initial width and finish
// the first layout
if (mCurrentViewWidth == 0 || !mBrowserFrame.firstLayoutDone()) {
@@ -1317,14 +1386,14 @@ final class WebViewCore {
}
// only fire an event if this is our first request
synchronized (this) {
- if (mDrawIsScheduled) {
+ if (mDrawIsPaused || mDrawIsScheduled) {
return;
}
mDrawIsScheduled = true;
mEventHub.sendMessage(Message.obtain(null, EventHub.WEBKIT_DRAW));
}
}
-
+
// called by JNI
private void contentScrollBy(int dx, int dy) {
if (!mBrowserFrame.firstLayoutDone()) {
@@ -1397,6 +1466,7 @@ final class WebViewCore {
sWebCoreHandler.removeMessages(WebCoreThread.CACHE_TICKER);
sWebCoreHandler.sendMessage(sWebCoreHandler
.obtainMessage(WebCoreThread.CACHE_TICKER));
+ contentDraw();
}
// called by JNI
@@ -1408,9 +1478,9 @@ final class WebViewCore {
}
// called by JNI
- private void sendViewInvalidate() {
+ private void sendViewInvalidate(int left, int top, int right, int bottom) {
if (mWebView != null) {
- mWebView.postInvalidate();
+ mWebView.postInvalidate(left, top, right, bottom);
}
}
@@ -1421,7 +1491,7 @@ final class WebViewCore {
private native void setViewportSettingsFromNative();
// called by JNI
- private void didFirstLayout(String url) {
+ private void didFirstLayout() {
// Trick to ensure that the Picture has the exact height for the content
// by forcing to layout with 0 height after the page is ready, which is
// indicated by didFirstLayout. This is essential to get rid of the
@@ -1435,7 +1505,7 @@ final class WebViewCore {
mWebView.mLastHeightSent, -1.0f));
}
- mBrowserFrame.didFirstLayout(url);
+ mBrowserFrame.didFirstLayout();
// reset the scroll position as it is a new page now
mWebkitScrollX = mWebkitScrollY = 0;
@@ -1517,7 +1587,16 @@ final class WebViewCore {
mRestoredScale = scale;
}
}
-
+
+ // called by JNI
+ private void needTouchEvents(boolean need) {
+ if (mWebView != null) {
+ Message.obtain(mWebView.mPrivateHandler,
+ WebView.WEBCORE_NEED_TOUCH_EVENTS, need ? 1 : 0, 0)
+ .sendToTarget();
+ }
+ }
+
// called by JNI
private void updateTextfield(int ptr, boolean changeToPassword,
String text, int textGeneration) {
@@ -1530,9 +1609,10 @@ final class WebViewCore {
}
}
- // these must be in document space (i.e. not scaled/zoomed.
- private native void nativeSetVisibleRect(int x, int y, int width,
- int height);
+ // these must be in document space (i.e. not scaled/zoomed).
+ private native void nativeSetScrollOffset(int dx, int dy);
+
+ private native void nativeSetGlobalBounds(int x, int y, int w, int h);
// called by JNI
private void requestListBox(String[] array, boolean[] enabledArray,
diff --git a/core/java/android/webkit/WebViewDatabase.java b/core/java/android/webkit/WebViewDatabase.java
index b367e27..96f3698 100644
--- a/core/java/android/webkit/WebViewDatabase.java
+++ b/core/java/android/webkit/WebViewDatabase.java
@@ -39,7 +39,7 @@ public class WebViewDatabase {
// log tag
protected static final String LOGTAG = "webviewdatabase";
- private static final int DATABASE_VERSION = 8;
+ private static final int DATABASE_VERSION = 9;
// 2 -> 3 Modified Cache table to allow cache of redirects
// 3 -> 4 Added Oma-Downloads table
// 4 -> 5 Modified Cache table to support persistent contentLength
@@ -47,6 +47,7 @@ public class WebViewDatabase {
// 5 -> 6 Add INDEX for cache table
// 6 -> 7 Change cache localPath from int to String
// 7 -> 8 Move cache to its own db
+ // 8 -> 9 Store both scheme and host when storing passwords
private static final int CACHE_DATABASE_VERSION = 1;
private static WebViewDatabase mInstance = null;
@@ -172,7 +173,6 @@ public class WebViewDatabase {
mDatabase.beginTransaction();
try {
upgradeDatabase();
- bootstrapDatabase();
mDatabase.setTransactionSuccessful();
} finally {
mDatabase.endTransaction();
@@ -200,6 +200,10 @@ public class WebViewDatabase {
} finally {
mCacheDatabase.endTransaction();
}
+ // Erase the files from the file system in the
+ // case that the database was updated and the
+ // there were existing cache content
+ CacheManager.removeAllCacheFiles();
}
if (mCacheDatabase != null) {
@@ -237,24 +241,26 @@ public class WebViewDatabase {
if (oldVersion != 0) {
Log.i(LOGTAG, "Upgrading database from version "
+ oldVersion + " to "
- + DATABASE_VERSION + ", which will destroy all old data");
+ + DATABASE_VERSION + ", which will destroy old data");
+ }
+ boolean justPasswords = 8 == oldVersion && 9 == DATABASE_VERSION;
+ if (!justPasswords) {
+ mDatabase.execSQL("DROP TABLE IF EXISTS "
+ + mTableNames[TABLE_COOKIES_ID]);
+ mDatabase.execSQL("DROP TABLE IF EXISTS cache");
+ mDatabase.execSQL("DROP TABLE IF EXISTS "
+ + mTableNames[TABLE_FORMURL_ID]);
+ mDatabase.execSQL("DROP TABLE IF EXISTS "
+ + mTableNames[TABLE_FORMDATA_ID]);
+ mDatabase.execSQL("DROP TABLE IF EXISTS "
+ + mTableNames[TABLE_HTTPAUTH_ID]);
}
mDatabase.execSQL("DROP TABLE IF EXISTS "
- + mTableNames[TABLE_COOKIES_ID]);
- mDatabase.execSQL("DROP TABLE IF EXISTS cache");
- mDatabase.execSQL("DROP TABLE IF EXISTS "
+ mTableNames[TABLE_PASSWORD_ID]);
- mDatabase.execSQL("DROP TABLE IF EXISTS "
- + mTableNames[TABLE_FORMURL_ID]);
- mDatabase.execSQL("DROP TABLE IF EXISTS "
- + mTableNames[TABLE_FORMDATA_ID]);
- mDatabase.execSQL("DROP TABLE IF EXISTS "
- + mTableNames[TABLE_HTTPAUTH_ID]);
+
mDatabase.setVersion(DATABASE_VERSION);
- }
- private static void bootstrapDatabase() {
- if (mDatabase != null) {
+ if (!justPasswords) {
// cookies
mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_COOKIES_ID]
+ " (" + ID_COL + " INTEGER PRIMARY KEY, "
@@ -265,14 +271,6 @@ public class WebViewDatabase {
mDatabase.execSQL("CREATE INDEX cookiesIndex ON "
+ mTableNames[TABLE_COOKIES_ID] + " (path)");
- // password
- mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_PASSWORD_ID]
- + " (" + ID_COL + " INTEGER PRIMARY KEY, "
- + PASSWORD_HOST_COL + " TEXT, " + PASSWORD_USERNAME_COL
- + " TEXT, " + PASSWORD_PASSWORD_COL + " TEXT," + " UNIQUE ("
- + PASSWORD_HOST_COL + ", " + PASSWORD_USERNAME_COL
- + ") ON CONFLICT REPLACE);");
-
// formurl
mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_FORMURL_ID]
+ " (" + ID_COL + " INTEGER PRIMARY KEY, " + FORMURL_URL_COL
@@ -295,6 +293,13 @@ public class WebViewDatabase {
+ HTTPAUTH_HOST_COL + ", " + HTTPAUTH_REALM_COL + ", "
+ HTTPAUTH_USERNAME_COL + ") ON CONFLICT REPLACE);");
}
+ // passwords
+ mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_PASSWORD_ID]
+ + " (" + ID_COL + " INTEGER PRIMARY KEY, "
+ + PASSWORD_HOST_COL + " TEXT, " + PASSWORD_USERNAME_COL
+ + " TEXT, " + PASSWORD_PASSWORD_COL + " TEXT," + " UNIQUE ("
+ + PASSWORD_HOST_COL + ", " + PASSWORD_USERNAME_COL
+ + ") ON CONFLICT REPLACE);");
}
private static void upgradeCacheDatabase() {
@@ -639,10 +644,11 @@ public class WebViewDatabase {
if (cursor.moveToFirst()) {
int batchSize = 100;
StringBuilder pathStr = new StringBuilder(20 + 16 * batchSize);
- pathStr.append("DELETE FROM cache WHERE filepath = ?");
+ pathStr.append("DELETE FROM cache WHERE filepath IN (?");
for (int i = 1; i < batchSize; i++) {
- pathStr.append(" OR filepath = ?");
+ pathStr.append(", ?");
}
+ pathStr.append(")");
SQLiteStatement statement = mCacheDatabase.compileStatement(pathStr
.toString());
// as bindString() uses 1-based index, initialize index to 1
@@ -658,6 +664,7 @@ public class WebViewDatabase {
pathList.add(filePath);
if (index++ == batchSize) {
statement.execute();
+ statement.clearBindings();
index = 1;
}
} while (cursor.moveToNext() && amount > 0);
@@ -679,19 +686,20 @@ public class WebViewDatabase {
/**
* Set password. Tuple (PASSWORD_HOST_COL, PASSWORD_USERNAME_COL) is unique.
*
- * @param host The host for the password
+ * @param schemePlusHost The scheme and host for the password
* @param username The username for the password. If it is null, it means
* password can't be saved.
* @param password The password
*/
- void setUsernamePassword(String host, String username, String password) {
- if (host == null || mDatabase == null) {
+ void setUsernamePassword(String schemePlusHost, String username,
+ String password) {
+ if (schemePlusHost == null || mDatabase == null) {
return;
}
synchronized (mPasswordLock) {
final ContentValues c = new ContentValues();
- c.put(PASSWORD_HOST_COL, host);
+ c.put(PASSWORD_HOST_COL, schemePlusHost);
c.put(PASSWORD_USERNAME_COL, username);
c.put(PASSWORD_PASSWORD_COL, password);
mDatabase.insert(mTableNames[TABLE_PASSWORD_ID], PASSWORD_HOST_COL,
@@ -702,12 +710,12 @@ public class WebViewDatabase {
/**
* Retrieve the username and password for a given host
*
- * @param host The host which passwords applies to
+ * @param schemePlusHost The scheme and host which passwords applies to
* @return String[] if found, String[0] is username, which can be null and
* String[1] is password. Return null if it can't find anything.
*/
- String[] getUsernamePassword(String host) {
- if (host == null || mDatabase == null) {
+ String[] getUsernamePassword(String schemePlusHost) {
+ if (schemePlusHost == null || mDatabase == null) {
return null;
}
@@ -718,8 +726,8 @@ public class WebViewDatabase {
synchronized (mPasswordLock) {
String[] ret = null;
Cursor cursor = mDatabase.query(mTableNames[TABLE_PASSWORD_ID],
- columns, selection, new String[] { host }, null, null,
- null);
+ columns, selection, new String[] { schemePlusHost }, null,
+ null, null);
if (cursor.moveToFirst()) {
ret = new String[2];
ret[0] = cursor.getString(
diff --git a/core/java/android/webkit/gears/AndroidWifiDataProvider.java b/core/java/android/webkit/gears/AndroidWifiDataProvider.java
new file mode 100644
index 0000000..7379f59
--- /dev/null
+++ b/core/java/android/webkit/gears/AndroidWifiDataProvider.java
@@ -0,0 +1,136 @@
+// Copyright 2008, Google Inc.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+// 3. Neither the name of Google Inc. nor the names of its contributors may be
+// used to endorse or promote products derived from this software without
+// specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package android.webkit.gears;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Config;
+import android.util.Log;
+import android.webkit.WebView;
+import java.util.List;
+
+/**
+ * WiFi data provider implementation for Android.
+ * {@hide}
+ */
+public final class AndroidWifiDataProvider extends BroadcastReceiver {
+ /**
+ * Logging tag
+ */
+ private static final String TAG = "Gears-J-WifiProvider";
+ /**
+ * Our Wifi manager instance.
+ */
+ private WifiManager mWifiManager;
+ /**
+ * The native object ID.
+ */
+ private long mNativeObject;
+ /**
+ * The Context instance.
+ */
+ private Context mContext;
+
+ /**
+ * Constructs a instance of this class and registers for wifi scan
+ * updates. Note that this constructor must be called on a Looper
+ * thread. Suitable threads can be created on the native side using
+ * the AndroidLooperThread C++ class.
+ */
+ public AndroidWifiDataProvider(WebView webview, long object) {
+ mNativeObject = object;
+ mContext = webview.getContext();
+ mWifiManager =
+ (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+ if (mWifiManager == null) {
+ Log.e(TAG,
+ "AndroidWifiDataProvider: could not get location manager.");
+ throw new NullPointerException(
+ "AndroidWifiDataProvider: locationManager is null.");
+ }
+
+ // Create a Handler that identifies the message loop associated
+ // with the current thread. Note that it is not necessary to
+ // override handleMessage() at all since the Intent
+ // ReceiverDispatcher (see the ActivityThread class) only uses
+ // this handler to post a Runnable to this thread's loop.
+ Handler handler = new Handler(Looper.myLooper());
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(mWifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
+ mContext.registerReceiver(this, filter, null, handler);
+
+ // Get the last scan results and pass them to the native side.
+ // We can't just invoke the callback here, so we queue a message
+ // to this thread's loop.
+ handler.post(new Runnable() {
+ public void run() {
+ onUpdateAvailable(mWifiManager.getScanResults(), mNativeObject);
+ }
+ });
+ }
+
+ /**
+ * Called when the provider is no longer needed.
+ */
+ public void shutdown() {
+ mContext.unregisterReceiver(this);
+ if (Config.LOGV) {
+ Log.v(TAG, "Wifi provider closed.");
+ }
+ }
+
+ /**
+ * This method is called when the AndroidWifiDataProvider is receiving an
+ * Intent broadcast.
+ * @param context The Context in which the receiver is running.
+ * @param intent The Intent being received.
+ */
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(
+ mWifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
+ if (Config.LOGV) {
+ Log.v(TAG, "Wifi scan resulst available");
+ }
+ onUpdateAvailable(mWifiManager.getScanResults(), mNativeObject);
+ }
+ }
+
+ /**
+ * The native method called when new wifi data is available.
+ * @param scanResults is a list of ScanResults to pass to the native side.
+ * @param nativeObject is a pointer to the corresponding
+ * AndroidWifiDataProvider C++ instance.
+ */
+ private static native void onUpdateAvailable(
+ List<ScanResult> scanResults, long nativeObject);
+}
diff --git a/core/java/android/webkit/gears/DesktopAndroid.java b/core/java/android/webkit/gears/DesktopAndroid.java
index 00a9a47..ee8ca49 100644
--- a/core/java/android/webkit/gears/DesktopAndroid.java
+++ b/core/java/android/webkit/gears/DesktopAndroid.java
@@ -40,8 +40,6 @@ import android.webkit.WebView;
public class DesktopAndroid {
private static final String TAG = "Gears-J-Desktop";
- private static final String BROWSER = "com.android.browser";
- private static final String BROWSER_ACTIVITY = BROWSER + ".BrowserActivity";
private static final String EXTRA_SHORTCUT_DUPLICATE = "duplicate";
private static final String ACTION_INSTALL_SHORTCUT =
"com.android.launcher.action.INSTALL_SHORTCUT";
@@ -78,11 +76,9 @@ public class DesktopAndroid {
String url, String imagePath) {
Context context = webview.getContext();
- ComponentName browser = new ComponentName(BROWSER, BROWSER_ACTIVITY);
-
Intent viewWebPage = new Intent(Intent.ACTION_VIEW);
- viewWebPage.setComponent(browser);
viewWebPage.setData(Uri.parse(url));
+ viewWebPage.addCategory(Intent.CATEGORY_BROWSABLE);
Intent intent = new Intent(ACTION_INSTALL_SHORTCUT);
intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, viewWebPage);
diff --git a/core/java/android/webkit/gears/HttpRequestAndroid.java b/core/java/android/webkit/gears/HttpRequestAndroid.java
index 8668c54..30f855f 100644
--- a/core/java/android/webkit/gears/HttpRequestAndroid.java
+++ b/core/java/android/webkit/gears/HttpRequestAndroid.java
@@ -163,7 +163,20 @@ public final class HttpRequestAndroid {
// Setup the connection. This doesn't go to the wire yet - it
// doesn't block.
try {
- connection = (HttpURLConnection) new URL(url).openConnection();
+ URL url_object = new URL(url);
+ // Check that the protocol is indeed HTTP(S).
+ String protocol = url_object.getProtocol();
+ if (protocol == null) {
+ log("null protocol for URL " + url);
+ return false;
+ }
+ protocol = protocol.toLowerCase();
+ if (!"http".equals(protocol) && !"https".equals(protocol)) {
+ log("Url has wrong protocol: " + url);
+ return false;
+ }
+
+ connection = (HttpURLConnection) url_object.openConnection();
connection.setRequestMethod(method);
// Manually follow redirects.
connection.setInstanceFollowRedirects(false);
@@ -197,11 +210,13 @@ public final class HttpRequestAndroid {
log("interrupt() called but no child thread");
return;
}
- if (inBlockingOperation) {
- log("Interrupting blocking operation");
- childThread.interrupt();
- } else {
- log("Nothing to interrupt");
+ synchronized (this) {
+ if (inBlockingOperation) {
+ log("Interrupting blocking operation");
+ childThread.interrupt();
+ } else {
+ log("Nothing to interrupt");
+ }
}
}
@@ -472,7 +487,7 @@ public final class HttpRequestAndroid {
String encoding = cacheResult.getEncoding();
// Encoding may not be specified. No default.
String contentType = mimeType;
- if (encoding != null) {
+ if (encoding != null && encoding.length() > 0) {
contentType += "; charset=" + encoding;
}
setResponseHeader(KEY_CONTENT_TYPE, contentType);
diff --git a/core/java/android/webkit/gears/NativeDialog.java b/core/java/android/webkit/gears/NativeDialog.java
new file mode 100644
index 0000000..9e2b375
--- /dev/null
+++ b/core/java/android/webkit/gears/NativeDialog.java
@@ -0,0 +1,142 @@
+// Copyright 2008 The Android Open Source Project
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+// 3. Neither the name of Google Inc. nor the names of its contributors may be
+// used to endorse or promote products derived from this software without
+// specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package android.webkit.gears;
+
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import java.io.File;
+import java.lang.InterruptedException;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Utility class to call a modal native dialog on Android
+ * The dialog itself is an Activity defined in the Browser.
+ * @hide
+ */
+public class NativeDialog {
+
+ private static final String TAG = "Gears-J-NativeDialog";
+
+ private final String DIALOG_PACKAGE = "com.android.browser";
+ private final String DIALOG_CLASS = DIALOG_PACKAGE + ".GearsNativeDialog";
+
+ private static Lock mLock = new ReentrantLock();
+ private static Condition mDialogFinished = mLock.newCondition();
+ private static String mResults = null;
+
+ private static boolean mAsynchronousDialog;
+
+ /**
+ * Utility function to build the intent calling the
+ * dialog activity
+ */
+ private Intent createIntent(String type, String arguments) {
+ Intent intent = new Intent();
+ intent.setClassName(DIALOG_PACKAGE, DIALOG_CLASS);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra("dialogArguments", arguments);
+ intent.putExtra("dialogType", type);
+ return intent;
+ }
+
+ /**
+ * Opens a native dialog synchronously and waits for its completion.
+ *
+ * The dialog is an activity (GearsNativeDialog) provided by the Browser
+ * that we call via startActivity(). Contrary to a normal activity though,
+ * we need to block until it returns. To do so, we define a static lock
+ * object in this class, which GearsNativeDialog can unlock once done
+ */
+ public String showDialog(Context context, String file,
+ String arguments) {
+
+ try {
+ mAsynchronousDialog = false;
+ mLock.lock();
+ File path = new File(file);
+ String fileName = path.getName();
+ String type = fileName.substring(0, fileName.indexOf(".html"));
+ Intent intent = createIntent(type, arguments);
+
+ mResults = null;
+ context.startActivity(intent);
+ mDialogFinished.await();
+ } catch (InterruptedException e) {
+ Log.e(TAG, "exception e: " + e);
+ } catch (ActivityNotFoundException e) {
+ Log.e(TAG, "exception e: " + e);
+ } finally {
+ mLock.unlock();
+ }
+
+ return mResults;
+ }
+
+ /**
+ * Opens a native dialog asynchronously
+ *
+ * The dialog is an activity (GearsNativeDialog) provided by the
+ * Browser.
+ */
+ public void showAsyncDialog(Context context, String type,
+ String arguments) {
+ mAsynchronousDialog = true;
+ Intent intent = createIntent(type, arguments);
+ context.startActivity(intent);
+ }
+
+ /**
+ * Static method that GearsNativeDialog calls to unlock us
+ */
+ public static void signalFinishedDialog() {
+ if (!mAsynchronousDialog) {
+ mLock.lock();
+ mDialogFinished.signal();
+ mLock.unlock();
+ } else {
+ // we call the native callback
+ closeAsynchronousDialog(mResults);
+ }
+ }
+
+ /**
+ * Static method that GearsNativeDialog calls to set the
+ * dialog's result
+ */
+ public static void closeDialog(String res) {
+ mResults = res;
+ }
+
+ /**
+ * Native callback method
+ */
+ private native static void closeAsynchronousDialog(String res);
+}
diff --git a/core/java/android/webkit/gears/PluginSettings.java b/core/java/android/webkit/gears/PluginSettings.java
new file mode 100644
index 0000000..2d0cc13
--- /dev/null
+++ b/core/java/android/webkit/gears/PluginSettings.java
@@ -0,0 +1,79 @@
+// Copyright 2008 The Android Open Source Project
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+// 3. Neither the name of Google Inc. nor the names of its contributors may be
+// used to endorse or promote products derived from this software without
+// specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package android.webkit.gears;
+
+import android.content.Context;
+import android.util.Log;
+import android.webkit.Plugin;
+import android.webkit.Plugin.PreferencesClickHandler;
+
+/**
+ * Simple bridge class intercepting the click in the
+ * browser plugin list and calling the Gears settings
+ * dialog.
+ */
+public class PluginSettings {
+
+ private static final String TAG = "Gears-J-PluginSettings";
+ private Context mContext;
+
+ public PluginSettings(Plugin plugin) {
+ plugin.setClickHandler(new ClickHandler());
+ }
+
+ /**
+ * We do not call the dialog synchronously here as the main
+ * message loop would be blocked, so we call it via a secondary thread.
+ */
+ private class ClickHandler implements PreferencesClickHandler {
+ public void handleClickEvent(Context context) {
+ mContext = context.getApplicationContext();
+ Thread startDialog = new Thread(new StartDialog(context));
+ startDialog.start();
+ }
+ }
+
+ /**
+ * Simple wrapper class to call the gears native method in
+ * a separate thread (the native code will then instanciate a NativeDialog
+ * object which will start the GearsNativeDialog activity defined in
+ * the Browser).
+ */
+ private class StartDialog implements Runnable {
+ Context mContext;
+
+ public StartDialog(Context context) {
+ mContext = context;
+ }
+
+ public void run() {
+ runSettingsDialog(mContext);
+ }
+ }
+
+ private static native void runSettingsDialog(Context c);
+
+}
diff --git a/core/java/android/webkit/gears/UrlInterceptHandlerGears.java b/core/java/android/webkit/gears/UrlInterceptHandlerGears.java
index 95fc30f..288240e 100644
--- a/core/java/android/webkit/gears/UrlInterceptHandlerGears.java
+++ b/core/java/android/webkit/gears/UrlInterceptHandlerGears.java
@@ -407,6 +407,8 @@ public class UrlInterceptHandlerGears implements UrlInterceptHandler {
true); // forceCache
if (cacheResult == null) {
+ // With the no-cache policy we could end up
+ // with a null result
return null;
}
@@ -444,8 +446,7 @@ public class UrlInterceptHandlerGears implements UrlInterceptHandler {
// be used for input.
cacheResult = CacheManager.getCacheFile(gearsUrl, null);
if (cacheResult != null) {
- if (logEnabled)
- log("Returning surrogate result");
+ log("Returning surrogate result");
return cacheResult;
} else {
// Not an expected condition, but handle gracefully. Perhaps out
@@ -476,7 +477,10 @@ public class UrlInterceptHandlerGears implements UrlInterceptHandler {
}
/**
- * Convenience debug function. Calls Android logging mechanism.
+ * Convenience debug function. Calls the Android logging
+ * mechanism. logEnabled is not a constant, so if the string
+ * evaluation is potentially expensive, the caller also needs to
+ * check it.
* @param str String to log to the Android console.
*/
private void log(String str) {