diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 18:28:45 -0800 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 18:28:45 -0800 |
commit | d83a98f4ce9cfa908f5c54bbd70f03eec07e7553 (patch) | |
tree | 4b825dc642cb6eb9a060e54bf8d69288fbee4904 /core/java/android/webkit | |
parent | 076357b8567458d4b6dfdcf839ef751634cd2bfb (diff) | |
download | frameworks_base-d83a98f4ce9cfa908f5c54bbd70f03eec07e7553.zip frameworks_base-d83a98f4ce9cfa908f5c54bbd70f03eec07e7553.tar.gz frameworks_base-d83a98f4ce9cfa908f5c54bbd70f03eec07e7553.tar.bz2 |
auto import from //depot/cupcake/@135843
Diffstat (limited to 'core/java/android/webkit')
52 files changed, 0 insertions, 22729 deletions
diff --git a/core/java/android/webkit/BrowserFrame.java b/core/java/android/webkit/BrowserFrame.java deleted file mode 100644 index 451af6d..0000000 --- a/core/java/android/webkit/BrowserFrame.java +++ /dev/null @@ -1,786 +0,0 @@ -/* - * Copyright (C) 2006 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.webkit; - -import android.content.Context; -import android.content.res.AssetManager; -import android.graphics.Bitmap; -import android.net.ParseException; -import android.net.WebAddress; -import android.net.http.SslCertificate; -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.URLEncoder; -import java.util.HashMap; -import java.util.Iterator; - -class BrowserFrame extends Handler { - - private static final String LOGTAG = "webkit"; - - /** - * Cap the number of LoadListeners that will be instantiated, so - * we don't blow the GREF count. Attempting to queue more than - * this many requests will prompt an error() callback on the - * request's LoadListener - */ - private final static int MAX_OUTSTANDING_REQUESTS = 300; - - private final CallbackProxy mCallbackProxy; - private final WebSettings mSettings; - private final Context mContext; - private final WebViewDatabase mDatabase; - private final WebViewCore mWebViewCore; - /* package */ boolean mLoadInitFromJava; - private int mLoadType; - private boolean mFirstLayoutDone = true; - private boolean mCommitted = true; - - // Is this frame the main frame? - private boolean mIsMainFrame; - - // Attached Javascript interfaces - private HashMap mJSInterfaceMap; - - // message ids - // a message posted when a frame loading is completed - static final int FRAME_COMPLETED = 1001; - // a message posted when the user decides the policy - static final int POLICY_FUNCTION = 1003; - - // Note: need to keep these in sync with FrameLoaderTypes.h in native - static final int FRAME_LOADTYPE_STANDARD = 0; - static final int FRAME_LOADTYPE_BACK = 1; - static final int FRAME_LOADTYPE_FORWARD = 2; - static final int FRAME_LOADTYPE_INDEXEDBACKFORWARD = 3; - static final int FRAME_LOADTYPE_RELOAD = 4; - static final int FRAME_LOADTYPE_RELOADALLOWINGSTALEDATA = 5; - static final int FRAME_LOADTYPE_SAME = 6; - static final int FRAME_LOADTYPE_REDIRECT = 7; - static final int FRAME_LOADTYPE_REPLACE = 8; - - // A progress threshold to switch from history Picture to live Picture - private static final int TRANSITION_SWITCH_THRESHOLD = 75; - - // This is a field accessed by native code as well as package classes. - /*package*/ int mNativeFrame; - - // Static instance of a JWebCoreJavaBridge to handle timer and cookie - // requests from WebCore. - static JWebCoreJavaBridge sJavaBridge; - - /** - * Create a new BrowserFrame to be used in an application. - * @param context An application context to use when retrieving assets. - * @param w A WebViewCore used as the view for this frame. - * @param proxy A CallbackProxy for posting messages to the UI thread and - * querying a client for information. - * @param settings A WebSettings object that holds all settings. - * XXX: Called by WebCore thread. - */ - public BrowserFrame(Context context, WebViewCore w, CallbackProxy proxy, - WebSettings settings) { - // Create a global JWebCoreJavaBridge to handle timers and - // cookies in the WebCore thread. - if (sJavaBridge == null) { - sJavaBridge = new JWebCoreJavaBridge(); - // set WebCore native cache size - sJavaBridge.setCacheSize(4 * 1024 * 1024); - // initialize CacheManager - CacheManager.init(context); - // create CookieSyncManager with current Context - CookieSyncManager.createInstance(context); - } - AssetManager am = context.getAssets(); - nativeCreateFrame(w, am, proxy.getBackForwardList()); - - mSettings = settings; - mContext = context; - mCallbackProxy = proxy; - mDatabase = WebViewDatabase.getInstance(context); - mWebViewCore = w; - - if (Config.LOGV) { - Log.v(LOGTAG, "BrowserFrame constructor: this=" + this); - } - } - - /** - * Load a url from the network or the filesystem into the main frame. - * Following the same behaviour as Safari, javascript: URLs are not - * passed to the main frame, instead they are evaluated immediately. - * @param url The url to load. - */ - public void loadUrl(String url) { - mLoadInitFromJava = true; - if (URLUtil.isJavaScriptUrl(url)) { - // strip off the scheme and evaluate the string - stringByEvaluatingJavaScriptFromString( - url.substring("javascript:".length())); - } else { - nativeLoadUrl(url); - } - mLoadInitFromJava = false; - } - - /** - * Load the content as if it was loaded by the provided base URL. The - * failUrl is used as the history entry for the load data. If null or - * an empty string is passed for the failUrl, then no history entry is - * created. - * - * @param baseUrl Base URL used to resolve relative paths in the content - * @param data Content to render in the browser - * @param mimeType Mimetype of the data being passed in - * @param encoding Character set encoding of the provided data. - * @param failUrl URL to use if the content fails to load or null. - */ - public void loadData(String baseUrl, String data, String mimeType, - String encoding, String failUrl) { - mLoadInitFromJava = true; - if (failUrl == null) { - failUrl = ""; - } - if (data == null) { - data = ""; - } - - // Setup defaults for missing values. These defaults where taken from - // WebKit's WebFrame.mm - if (baseUrl == null || baseUrl.length() == 0) { - baseUrl = "about:blank"; - } - if (mimeType == null || mimeType.length() == 0) { - mimeType = "text/html"; - } - nativeLoadData(baseUrl, data, mimeType, encoding, failUrl); - mLoadInitFromJava = false; - } - - /** - * 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. - * @param description A String description. - * TODO: Report all errors including resource errors but include some kind - * of domain identifier. Change errorCode to an enum for a cleaner - * interface. - */ - private void reportError(final int errorCode, final String description, - final String failingUrl) { - // As this is called for the main resource and loading will be stopped - // after, reset the state variables. - mCommitted = true; - mWebViewCore.mEndScaleZoom = mFirstLayoutDone == false; - mFirstLayoutDone = true; - mCallbackProxy.onReceivedError(errorCode, description, failingUrl); - } - - /* package */boolean committed() { - return mCommitted; - } - - /* package */boolean firstLayoutDone() { - return mFirstLayoutDone; - } - - /* package */int loadType() { - return mLoadType; - } - - /* 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; - } - - /** - * native callback - * Indicates the beginning of a new load. - * This method will be called once for the main frame. - */ - private void loadStarted(String url, Bitmap favicon, int loadType, - boolean isMainFrame) { - mIsMainFrame = isMainFrame; - - if (isMainFrame || loadType == FRAME_LOADTYPE_STANDARD) { - mLoadType = loadType; - - if (isMainFrame) { - // Call onPageStarted for main frames. - mCallbackProxy.onPageStarted(url, favicon); - // as didFirstLayout() is only called for the main frame, reset - // mFirstLayoutDone only for the main frames - mFirstLayoutDone = false; - mCommitted = false; - // remove pending draw to block update until mFirstLayoutDone is - // set to true in didFirstLayout() - mWebViewCore.removeMessages(WebViewCore.EventHub.WEBKIT_DRAW); - } - - // Note: only saves committed form data in standard load - if (loadType == FRAME_LOADTYPE_STANDARD - && mSettings.getSaveFormData()) { - final WebHistoryItem h = mCallbackProxy.getBackForwardList() - .getCurrentItem(); - if (h != null) { - String currentUrl = h.getUrl(); - if (currentUrl != null) { - mDatabase.setFormData(currentUrl, getFormTextData()); - } - } - } - } - } - - /** - * native callback - * Indicates the WebKit has committed to the new load - */ - private void transitionToCommitted(int loadType, boolean isMainFrame) { - // loadType is not used yet - if (isMainFrame) { - mCommitted = true; - } - } - - /** - * native callback - * <p> - * Indicates the end of a new load. - * This method will be called once for the main frame. - */ - private void loadFinished(String url, int loadType, boolean isMainFrame) { - // mIsMainFrame and isMainFrame are better be equal!!! - - if (isMainFrame || loadType == FRAME_LOADTYPE_STANDARD) { - if (isMainFrame) { - mCallbackProxy.switchOutDrawHistory(); - mCallbackProxy.onPageFinished(url); - } - } - } - - /** - * We have received an SSL certificate for the main top-level page. - * - * !!!Called from the network thread!!! - */ - void certificate(SslCertificate certificate) { - if (mIsMainFrame) { - // we want to make this call even if the certificate is null - // (ie, the site is not secure) - mCallbackProxy.onReceivedCertificate(certificate); - } - } - - /** - * Destroy all native components of the BrowserFrame. - */ - public void destroy() { - nativeDestroyFrame(); - removeCallbacksAndMessages(null); - } - - /** - * Handle messages posted to us. - * @param msg The message to handle. - */ - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case FRAME_COMPLETED: { - if (mSettings.getSavePassword() && hasPasswordField()) { - if (Config.DEBUG) { - Assert.assertNotNull(mCallbackProxy.getBackForwardList() - .getCurrentItem()); - } - WebAddress uri = new WebAddress( - mCallbackProxy.getBackForwardList().getCurrentItem() - .getUrl()); - String schemePlusHost = uri.mScheme + uri.mHost; - String[] up = mDatabase.getUsernamePassword(schemePlusHost); - if (up != null && up[0] != null) { - setUsernamePassword(up[0], up[1]); - } - } - CacheManager.trimCacheIfNeeded(); - break; - } - - case POLICY_FUNCTION: { - nativeCallPolicyFunction(msg.arg1, msg.arg2); - break; - } - - default: - break; - } - } - - /** - * Punch-through for WebCore to set the document - * title. Inform the Activity of the new title. - * @param title The new title of the document. - */ - private void setTitle(String title) { - // FIXME: The activity must call getTitle (a native method) to get the - // title. We should try and cache the title if we can also keep it in - // sync with the document. - mCallbackProxy.onReceivedTitle(title); - } - - /** - * Retrieves the render tree of this frame and puts it as the object for - * the message and sends the message. - * @param callback the message to use to send the render tree - */ - public void externalRepresentation(Message callback) { - callback.obj = externalRepresentation();; - callback.sendToTarget(); - } - - /** - * Return the render tree as a string - */ - private native String externalRepresentation(); - - /** - * Retrieves the visual text of the current frame, puts it as the object for - * the message and sends the message. - * @param callback the message to use to send the visual text - */ - public void documentAsText(Message callback) { - callback.obj = documentAsText();; - callback.sendToTarget(); - } - - /** - * Return the text drawn on the screen as a string - */ - private native String documentAsText(); - - /* - * This method is called by WebCore to inform the frame that - * the Javascript window object has been cleared. - * We should re-attach any attached js interfaces. - */ - private void windowObjectCleared(int nativeFramePointer) { - if (mJSInterfaceMap != null) { - Iterator iter = mJSInterfaceMap.keySet().iterator(); - while (iter.hasNext()) { - String interfaceName = (String) iter.next(); - nativeAddJavascriptInterface(nativeFramePointer, - mJSInterfaceMap.get(interfaceName), interfaceName); - } - } - } - - /** - * This method is called by WebCore to check whether application - * wants to hijack url loading - */ - public boolean handleUrl(String url) { - if (mLoadInitFromJava == true) { - return false; - } - 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) { - if (mJSInterfaceMap == null) { - mJSInterfaceMap = new HashMap<String, Object>(); - } - if (mJSInterfaceMap.containsKey(interfaceName)) { - mJSInterfaceMap.remove(interfaceName); - } - mJSInterfaceMap.put(interfaceName, obj); - } - - /** - * Start loading a resource. - * @param loaderHandle The native ResourceLoader that is the target of the - * data. - * @param url The url to load. - * @param method The http method. - * @param headers The http headers. - * @param postData If the method is "POST" postData is sent as the request - * 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. - * @param synchronous True if the load is synchronous. - * @return A newly created LoadListener object. - */ - private LoadListener startLoadingResource(int loaderHandle, - String url, - String method, - HashMap headers, - byte[] postData, - int cacheMode, - boolean isHighPriority, - boolean synchronous) { - PerfChecker checker = new PerfChecker(); - - if (mSettings.getCacheMode() != WebSettings.LOAD_DEFAULT) { - cacheMode = mSettings.getCacheMode(); - } - - if (method.equals("POST")) { - // Don't use the cache on POSTs when issuing a normal POST - // request. - if (cacheMode == WebSettings.LOAD_NORMAL) { - cacheMode = WebSettings.LOAD_NO_CACHE; - } - if (mSettings.getSavePassword() && hasPasswordField()) { - try { - if (Config.DEBUG) { - Assert.assertNotNull(mCallbackProxy.getBackForwardList() - .getCurrentItem()); - } - WebAddress uri = new WebAddress(mCallbackProxy - .getBackForwardList().getCurrentItem().getUrl()); - String schemePlusHost = uri.mScheme + uri.mHost; - String[] ret = getUsernamePassword(); - // 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); - } - } - } - } catch (ParseException ex) { - // if it is bad uri, don't save its password - } - - } - } - - // is this resource the main-frame top-level page? - boolean isMainFramePage = mIsMainFrame; - - if (Config.LOGV) { - Log.v(LOGTAG, "startLoadingResource: url=" + url + ", method=" - + method + ", postData=" + postData + ", isHighPriority=" - + isHighPriority + ", isMainFramePage=" + isMainFramePage); - } - - // Create a LoadListener - LoadListener loadListener = LoadListener.getLoadListener(mContext, this, url, - loaderHandle, synchronous, isMainFramePage); - - mCallbackProxy.onLoadResource(url); - - if (LoadListener.getNativeLoaderCount() > MAX_OUTSTANDING_REQUESTS) { - loadListener.error( - android.net.http.EventHandler.ERROR, mContext.getString( - com.android.internal.R.string.httpErrorTooManyRequests)); - loadListener.notifyError(); - loadListener.tearDown(); - return null; - } - - // during synchronous load, the WebViewCore thread is blocked, so we - // need to endCacheTransaction first so that http thread won't be - // blocked in setupFile() when createCacheFile. - if (synchronous) { - CacheManager.endCacheTransaction(); - } - - 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 - // for the current page. - // Set referrer to current URL? - if (!loader.executeLoad()) { - checker.responseAlert("startLoadingResource fail"); - } - checker.responseAlert("startLoadingResource succeed"); - - if (synchronous) { - CacheManager.startCacheTransaction(); - } - - return !synchronous ? loadListener : null; - } - - /** - * Set the progress for the browser activity. Called by native code. - * Uses a delay so it does not happen too often. - * @param newProgress An int between zero and one hundred representing - * the current progress percentage of loading the page. - */ - private void setProgress(int newProgress) { - mCallbackProxy.onProgressChanged(newProgress); - if (newProgress == 100) { - sendMessageDelayed(obtainMessage(FRAME_COMPLETED), 100); - } - // FIXME: Need to figure out a better way to switch out of the history - // drawing mode. Maybe we can somehow compare the history picture with - // the current picture, and switch when it contains more content. - if (mFirstLayoutDone && newProgress > TRANSITION_SWITCH_THRESHOLD) { - mCallbackProxy.switchOutDrawHistory(); - } - } - - /** - * Send the icon to the activity for display. - * @param icon A Bitmap representing a page's favicon. - */ - private void didReceiveIcon(Bitmap icon) { - mCallbackProxy.onReceivedIcon(icon); - } - - /** - * Request a new window from the client. - * @return The BrowserFrame object stored in the new WebView. - */ - private BrowserFrame createWindow(boolean dialog, boolean userGesture) { - WebView w = mCallbackProxy.createWindow(dialog, userGesture); - if (w != null) { - return w.getWebViewCore().getBrowserFrame(); - } - return null; - } - - /** - * Try to focus this WebView. - */ - private void requestFocus() { - mCallbackProxy.onRequestFocus(); - } - - /** - * Close this frame and window. - */ - private void closeWindow(WebViewCore w) { - mCallbackProxy.onCloseWindow(w.getWebView()); - } - - // XXX: Must match PolicyAction in FrameLoaderTypes.h in webcore - static final int POLICY_USE = 0; - static final int POLICY_IGNORE = 2; - - private void decidePolicyForFormResubmission(int policyFunction) { - Message dontResend = obtainMessage(POLICY_FUNCTION, policyFunction, - POLICY_IGNORE); - Message resend = obtainMessage(POLICY_FUNCTION, policyFunction, - POLICY_USE); - mCallbackProxy.onFormResubmission(dontResend, resend); - } - - /** - * Tell the activity to update its global history. - */ - private void updateVisitedHistory(String url, boolean isReload) { - mCallbackProxy.doUpdateVisitedHistory(url, isReload); - } - - /** - * Get the CallbackProxy for sending messages to the UI thread. - */ - /* package */ CallbackProxy getCallbackProxy() { - return mCallbackProxy; - } - - /** - * Returns the User Agent used by this frame - */ - String getUserAgentString() { - 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 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(WebViewCore w, AssetManager am, - WebBackForwardList list); - - /** - * Destroy the native frame. - */ - public native void nativeDestroyFrame(); - - private native void nativeCallPolicyFunction(int policyFunction, - int decision); - - /** - * Reload the current main frame. - */ - public native void reload(boolean allowStale); - - /** - * 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. - */ - private native void nativeGoBackOrForward(int steps); - - /** - * stringByEvaluatingJavaScriptFromString will execute the - * JS passed in in the context of this browser frame. - * @param script A javascript string to execute - * - * @return string result of execution or null - */ - public native String stringByEvaluatingJavaScriptFromString(String script); - - /** - * Add a javascript interface to the main frame. - */ - private native void nativeAddJavascriptInterface(int nativeFramePointer, - Object obj, String interfaceName); - - /** - * Enable or disable the native cache. - */ - /* FIXME: The native cache is always on for now until we have a better - * solution for our 2 caches. */ - private native void setCacheDisabled(boolean disabled); - - public native boolean cacheDisabled(); - - public native void clearCache(); - - /** - * Returns false if the url is bad. - */ - private native void nativeLoadUrl(String url); - - private native void nativeLoadData(String baseUrl, String data, - String mimeType, String encoding, String failUrl); - - /** - * Stop loading the current page. - */ - public native void stopLoading(); - - /** - * Return true if the document has images. - */ - public native boolean documentHasImages(); - - /** - * @return TRUE if there is a password field in the current frame - */ - private native boolean hasPasswordField(); - - /** - * Get username and password in the current frame. If found, String[0] is - * username and String[1] is password. Otherwise return NULL. - * @return String[] - */ - private native String[] getUsernamePassword(); - - /** - * Set username and password to the proper fields in the current frame - * @param username - * @param password - */ - private native void setUsernamePassword(String username, String password); - - /** - * Get form's "text" type data associated with the current frame. - * @return HashMap If succeed, returns a list of name/value pair. Otherwise - * returns null. - */ - private native HashMap getFormTextData(); -} diff --git a/core/java/android/webkit/ByteArrayBuilder.java b/core/java/android/webkit/ByteArrayBuilder.java deleted file mode 100644 index 806b458..0000000 --- a/core/java/android/webkit/ByteArrayBuilder.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright (C) 2006 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.webkit; - -import java.util.LinkedList; - -/** Utility class optimized for accumulating bytes, and then spitting - them back out. It does not optimize for returning the result in a - single array, though this is supported in the API. It is fastest - if the retrieval can be done via iterating through chunks. - - Things to add: - - consider dynamically increasing our min_capacity, - as we see mTotalSize increase -*/ -class ByteArrayBuilder { - - private static final int DEFAULT_CAPACITY = 8192; - - private LinkedList<Chunk> mChunks; - - /** free pool */ - private LinkedList<Chunk> mPool; - - private int mMinCapacity; - - public ByteArrayBuilder() { - init(0); - } - - public ByteArrayBuilder(int minCapacity) { - init(minCapacity); - } - - private void init(int minCapacity) { - mChunks = new LinkedList<Chunk>(); - mPool = new LinkedList<Chunk>(); - - if (minCapacity <= 0) { - minCapacity = DEFAULT_CAPACITY; - } - mMinCapacity = minCapacity; - } - - public void append(byte[] array) { - append(array, 0, array.length); - } - - public synchronized void append(byte[] array, int offset, int length) { - while (length > 0) { - Chunk c = appendChunk(length); - int amount = Math.min(length, c.mArray.length - c.mLength); - System.arraycopy(array, offset, c.mArray, c.mLength, amount); - c.mLength += amount; - length -= amount; - offset += amount; - } - } - - /** - * The fastest way to retrieve the data is to iterate through the - * chunks. This returns the first chunk. Note: this pulls the - * chunk out of the queue. The caller must call releaseChunk() to - * dispose of it. - */ - public synchronized Chunk getFirstChunk() { - if (mChunks.isEmpty()) return null; - return mChunks.removeFirst(); - } - - /** - * recycles chunk - */ - public synchronized void releaseChunk(Chunk c) { - c.mLength = 0; - mPool.addLast(c); - } - - public boolean isEmpty() { - return mChunks.isEmpty(); - } - - public synchronized void clear() { - Chunk c = getFirstChunk(); - while (c != null) { - releaseChunk(c); - c = getFirstChunk(); - } - } - - private Chunk appendChunk(int length) { - if (length < mMinCapacity) { - length = mMinCapacity; - } - - Chunk c; - if (mChunks.isEmpty()) { - c = obtainChunk(length); - } else { - c = mChunks.getLast(); - if (c.mLength == c.mArray.length) { - c = obtainChunk(length); - } - } - return c; - } - - private Chunk obtainChunk(int length) { - Chunk c; - if (mPool.isEmpty()) { - c = new Chunk(length); - } else { - c = mPool.removeFirst(); - } - mChunks.addLast(c); - return c; - } - - public static class Chunk { - public byte[] mArray; - public int mLength; - - public Chunk(int length) { - mArray = new byte[length]; - mLength = 0; - } - } -} diff --git a/core/java/android/webkit/CacheLoader.java b/core/java/android/webkit/CacheLoader.java deleted file mode 100644 index 3e1b602..0000000 --- a/core/java/android/webkit/CacheLoader.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.webkit; - -import android.net.http.Headers; - -/** - * This class is a concrete implementation of StreamLoader that uses a - * CacheResult as the source for the stream. The CacheResult stored mimetype - * and encoding is added to the HTTP response headers. - */ -class CacheLoader extends StreamLoader { - - CacheManager.CacheResult mCacheResult; // Content source - - /** - * Constructs a CacheLoader for use when loading content from the cache. - * - * @param loadListener LoadListener to pass the content to - * @param result CacheResult used as the source for the content. - */ - CacheLoader(LoadListener loadListener, CacheManager.CacheResult result) { - super(loadListener); - mCacheResult = result; - } - - @Override - protected boolean setupStreamAndSendStatus() { - mDataStream = mCacheResult.inStream; - mContentLength = mCacheResult.contentLength; - mHandler.status(1, 1, mCacheResult.httpStatusCode, "OK"); - return true; - } - - @Override - protected void buildHeaders(Headers headers) { - StringBuilder sb = new StringBuilder(mCacheResult.mimeType); - if (mCacheResult.encoding != null && - mCacheResult.encoding.length() > 0) { - sb.append(';'); - sb.append(mCacheResult.encoding); - } - headers.setContentType(sb.toString()); - - if (mCacheResult.location != null && - mCacheResult.location.length() > 0) { - headers.setLocation(mCacheResult.location); - } - } - -} diff --git a/core/java/android/webkit/CacheManager.java b/core/java/android/webkit/CacheManager.java deleted file mode 100644 index d12940d..0000000 --- a/core/java/android/webkit/CacheManager.java +++ /dev/null @@ -1,703 +0,0 @@ -/* - * Copyright (C) 2006 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.webkit; - -import android.content.Context; -import android.net.http.Headers; -import android.os.FileUtils; -import android.util.Config; -import android.util.Log; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.Map; - -import org.bouncycastle.crypto.Digest; -import org.bouncycastle.crypto.digests.SHA1Digest; - -/** - * The class CacheManager provides the persistent cache of content that is - * received over the network. The component handles parsing of HTTP headers and - * utilizes the relevant cache headers to determine if the content should be - * stored and if so, how long it is valid for. Network requests are provided to - * this component and if they can not be resolved by the cache, the HTTP headers - * are attached, as appropriate, to the request for revalidation of content. The - * class also manages the cache size. - */ -public final class CacheManager { - - private static final String LOGTAG = "cache"; - - static final String HEADER_KEY_IFMODIFIEDSINCE = "if-modified-since"; - static final String HEADER_KEY_IFNONEMATCH = "if-none-match"; - - private static final String NO_STORE = "no-store"; - private static final String NO_CACHE = "no-cache"; - private static final String MAX_AGE = "max-age"; - - private static long CACHE_THRESHOLD = 6 * 1024 * 1024; - private static long CACHE_TRIM_AMOUNT = 2 * 1024 * 1024; - - private static boolean mDisabled; - - // 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 - int httpStatusCode; - long contentLength; - long expires; - String localPath; - String lastModified; - String etag; - String mimeType; - String location; - String encoding; - - // these fields are NOT saved to the database - InputStream inStream; - OutputStream outStream; - File outFile; - - public int getHttpStatusCode() { - return httpStatusCode; - } - - public long getContentLength() { - return contentLength; - } - - public String getLocalPath() { - return localPath; - } - - public long getExpires() { - return expires; - } - - public String getLastModified() { - return lastModified; - } - - public String getETag() { - return etag; - } - - public String getMimeType() { - return mimeType; - } - - public String getLocation() { - return location; - } - - public String getEncoding() { - return encoding; - } - - // For out-of-package access to the underlying streams. - public InputStream getInputStream() { - return inStream; - } - - public OutputStream getOutputStream() { - return outStream; - } - - // These fields can be set manually. - public void setInputStream(InputStream stream) { - this.inStream = stream; - } - - public void setEncoding(String encoding) { - this.encoding = encoding; - } - } - - /** - * initialize the CacheManager. WebView should handle this for each process. - * - * @param context The application context. - */ - 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 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; - } - - /** - * get the base directory of the cache. With localPath of the CacheResult, - * it identifies the cache file. - * - * @return File The base directory of the cache. - */ - public static File getCacheFileBaseDir() { - return mBaseDir; - } - - /** - * set the flag to control whether cache is enabled or disabled - * - * @param disabled true to disable the cache - */ - // only called from WebCore thread - static void setCacheDisabled(boolean disabled) { - if (disabled == mDisabled) { - return; - } - mDisabled = disabled; - if (mDisabled) { - removeAllCacheFiles(); - } - } - - /** - * get the state of the current cache, enabled or disabled - * - * @return return if it is disabled - */ - public static boolean cacheDisabled() { - return mDisabled; - } - - // only called from WebCore thread - // make sure to call enableTransaction/disableTransaction in pair - static boolean enableTransaction() { - if (++mRefCount == 1) { - mDataBase.startCacheTransaction(); - return true; - } - return false; - } - - // only called from WebCore thread - // make sure to call enableTransaction/disableTransaction in pair - static boolean disableTransaction() { - if (mRefCount == 0) { - Log.e(LOGTAG, "disableTransaction is out of sync"); - } - if (--mRefCount == 0) { - mDataBase.endCacheTransaction(); - return true; - } - return false; - } - - // only called from WebCore thread - // make sure to call startCacheTransaction/endCacheTransaction in pair - public static boolean startCacheTransaction() { - return mDataBase.startCacheTransaction(); - } - - // only called from WebCore thread - // make sure to call startCacheTransaction/endCacheTransaction in pair - public static boolean endCacheTransaction() { - boolean ret = mDataBase.endCacheTransaction(); - if (++mTrimCacheCount >= TRIM_CACHE_INTERVAL) { - mTrimCacheCount = 0; - trimCacheIfNeeded(); - } - return ret; - } - - /** - * Given a url, returns the CacheResult if exists. Otherwise returns null. - * If headers are provided and a cache needs validation, - * HEADER_KEY_IFNONEMATCH or HEADER_KEY_IFMODIFIEDSINCE will be set in the - * cached headers. - * - * @return the CacheResult for a given url - */ - // only called from WebCore thread - public static CacheResult getCacheFile(String url, - Map<String, String> headers) { - if (mDisabled) { - return null; - } - - CacheResult result = mDataBase.getCache(url); - if (result != null) { - if (result.contentLength == 0) { - if (result.httpStatusCode != 301 - && result.httpStatusCode != 302 - && result.httpStatusCode != 307) { - // this should not happen. If it does, remove it. - mDataBase.removeCache(url); - return null; - } - } else { - File src = new File(mBaseDir, result.localPath); - try { - // open here so that even the file is deleted, the content - // is still readable by the caller until close() is called - result.inStream = new FileInputStream(src); - } catch (FileNotFoundException e) { - // the files in the cache directory can be removed by the - // system. If it is gone, clean up the database - mDataBase.removeCache(url); - return null; - } - } - } else { - return null; - } - - // null headers request coming from CACHE_MODE_CACHE_ONLY - // which implies that it needs cache even it is expired. - // negative expires means time in the far future. - if (headers != null && result.expires >= 0 - && result.expires <= System.currentTimeMillis()) { - if (result.lastModified == null && result.etag == null) { - return null; - } - // return HEADER_KEY_IFNONEMATCH or HEADER_KEY_IFMODIFIEDSINCE - // for requesting validation - if (result.etag != null) { - headers.put(HEADER_KEY_IFNONEMATCH, result.etag); - } - if (result.lastModified != null) { - headers.put(HEADER_KEY_IFMODIFIEDSINCE, result.lastModified); - } - } - - if (Config.LOGV) { - Log.v(LOGTAG, "getCacheFile for url " + url); - } - - return result; - } - - /** - * Given a url and its full headers, returns CacheResult if a local cache - * can be stored. Otherwise returns null. The mimetype is passed in so that - * the function can use the mimetype that will be passed to WebCore which - * could be different from the mimetype defined in the headers. - * forceCache is for out-of-package callers to force creation of a - * CacheResult, and is used to supply surrogate responses for URL - * interception. - * @return CacheResult for a given url - * @hide - hide createCacheFile since it has a parameter of type headers, which is - * in a hidden package. - */ - // can be called from any thread - public static CacheResult createCacheFile(String url, int statusCode, - Headers headers, String mimeType, boolean forceCache) { - if (!forceCache && mDisabled) { - return null; - } - - CacheResult ret = parseHeaders(statusCode, headers, mimeType); - if (ret != null) { - setupFiles(url, ret); - try { - ret.outStream = new FileOutputStream(ret.outFile); - } catch (FileNotFoundException e) { - // 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; - } - - return ret; - } - - /** - * Save the info of a cache file for a given url to the CacheMap so that it - * can be reused later - */ - // only called from WebCore thread - public static void saveCacheFile(String url, CacheResult cacheRet) { - try { - cacheRet.outStream.close(); - } catch (IOException e) { - return; - } - - if (!cacheRet.outFile.exists()) { - // the file in the cache directory can be removed by the system - return; - } - - cacheRet.contentLength = cacheRet.outFile.length(); - if (cacheRet.httpStatusCode == 301 - || cacheRet.httpStatusCode == 302 - || cacheRet.httpStatusCode == 307) { - // location is in database, no need to keep the file - cacheRet.contentLength = 0; - cacheRet.localPath = new String(); - cacheRet.outFile.delete(); - } else if (cacheRet.contentLength == 0) { - cacheRet.outFile.delete(); - return; - } - - mDataBase.addCache(url, cacheRet); - - if (Config.LOGV) { - Log.v(LOGTAG, "saveCacheFile for url " + url); - } - } - - /** - * remove all cache files - * - * @return true if it succeeds - */ - // 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(); - // 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. - } - // delete database - mDataBase.clearCache(); - } - }; - new Thread(clearCache).start(); - return true; - } - - /** - * Return true if the cache is empty. - */ - // only called from WebCore thread - static boolean cacheEmpty() { - return mDataBase.hasCache(); - } - - // only called from WebCore thread - static void trimCacheIfNeeded() { - if (mDataBase.getCacheTotalSize() > CACHE_THRESHOLD) { - ArrayList<String> pathList = mDataBase.trimCache(CACHE_TRIM_AMOUNT); - int size = pathList.size(); - for (int i = 0; i < size; i++) { - new File(mBaseDir, pathList.get(i)).delete(); - } - } - } - - @SuppressWarnings("deprecation") - private static void setupFiles(String url, CacheResult cacheRet) { - if (true) { - // Note: SHA1 is much stronger hash. But the cost of setupFiles() is - // 3.2% cpu time for a fresh load of nytimes.com. While a simple - // String.hashCode() is only 0.6%. If adding the collision resolving - // to String.hashCode(), it makes the cpu time to be 1.6% for a - // fresh load, but 5.3% for the worst case where all the files - // already exist in the file system, but database is gone. So it - // needs to resolve collision for every file at least once. - int hashCode = url.hashCode(); - StringBuffer ret = new StringBuffer(8); - appendAsHex(hashCode, ret); - String path = ret.toString(); - File file = new File(mBaseDir, path); - if (true) { - boolean checkOldPath = true; - // Check hash collision. If the hash file doesn't exist, just - // continue. There is a chance that the old cache file is not - // same as the hash file. As mDataBase.getCache() is more - // expansive than "leak" a file until clear cache, don't bother. - // If the hash file exists, make sure that it is same as the - // cache file. If it is not, resolve the collision. - while (file.exists()) { - if (checkOldPath) { - // as this is called from http thread through - // createCacheFile, we need endCacheTransaction before - // database access. - WebViewCore.endCacheTransaction(); - CacheResult oldResult = mDataBase.getCache(url); - WebViewCore.startCacheTransaction(); - if (oldResult != null && oldResult.contentLength > 0) { - if (path.equals(oldResult.localPath)) { - path = oldResult.localPath; - } else { - path = oldResult.localPath; - file = new File(mBaseDir, path); - } - break; - } - checkOldPath = false; - } - ret = new StringBuffer(8); - appendAsHex(++hashCode, ret); - path = ret.toString(); - file = new File(mBaseDir, path); - } - } - cacheRet.localPath = path; - cacheRet.outFile = file; - } else { - // get hash in byte[] - Digest digest = new SHA1Digest(); - int digestLen = digest.getDigestSize(); - byte[] hash = new byte[digestLen]; - int urlLen = url.length(); - byte[] data = new byte[urlLen]; - url.getBytes(0, urlLen, data, 0); - digest.update(data, 0, urlLen); - digest.doFinal(hash, 0); - // convert byte[] to hex String - StringBuffer result = new StringBuffer(2 * digestLen); - for (int i = 0; i < digestLen; i = i + 4) { - int h = (0x00ff & hash[i]) << 24 | (0x00ff & hash[i + 1]) << 16 - | (0x00ff & hash[i + 2]) << 8 | (0x00ff & hash[i + 3]); - appendAsHex(h, result); - } - cacheRet.localPath = result.toString(); - cacheRet.outFile = new File(mBaseDir, cacheRet.localPath); - } - } - - private static void appendAsHex(int i, StringBuffer ret) { - String hex = Integer.toHexString(i); - switch (hex.length()) { - case 1: - ret.append("0000000"); - break; - case 2: - ret.append("000000"); - break; - case 3: - ret.append("00000"); - break; - case 4: - ret.append("0000"); - break; - case 5: - ret.append("000"); - break; - case 6: - ret.append("00"); - break; - case 7: - ret.append("0"); - break; - } - ret.append(hex); - } - - private static CacheResult parseHeaders(int statusCode, Headers headers, - String mimeType) { - // TODO: if authenticated or secure, return null - CacheResult ret = new CacheResult(); - ret.httpStatusCode = statusCode; - - String location = headers.getLocation(); - if (location != null) ret.location = location; - - ret.expires = -1; - String expires = headers.getExpires(); - if (expires != null) { - try { - ret.expires = HttpDateTime.parse(expires); - } catch (IllegalArgumentException ex) { - // Take care of the special "-1" and "0" cases - if ("-1".equals(expires) || "0".equals(expires)) { - // make it expired, but can be used for history navigation - ret.expires = 0; - } else { - Log.e(LOGTAG, "illegal expires: " + expires); - } - } - } - - String lastModified = headers.getLastModified(); - if (lastModified != null) ret.lastModified = lastModified; - - String etag = headers.getEtag(); - if (etag != null) ret.etag = etag; - - String cacheControl = headers.getCacheControl(); - if (cacheControl != null) { - String[] controls = cacheControl.toLowerCase().split("[ ,;]"); - for (int i = 0; i < controls.length; i++) { - if (NO_STORE.equals(controls[i])) { - return null; - } - // According to the spec, 'no-cache' means that the content - // must be re-validated on every load. It does not mean that - // the content can not be cached. set to expire 0 means it - // can only be used in CACHE_MODE_CACHE_ONLY case - if (NO_CACHE.equals(controls[i])) { - ret.expires = 0; - } else if (controls[i].startsWith(MAX_AGE)) { - int separator = controls[i].indexOf('='); - if (separator < 0) { - separator = controls[i].indexOf(':'); - } - if (separator > 0) { - String s = controls[i].substring(separator + 1); - try { - long sec = Long.parseLong(s); - if (sec >= 0) { - ret.expires = System.currentTimeMillis() + 1000 - * sec; - } - } catch (NumberFormatException ex) { - if ("1d".equals(s)) { - // Take care of the special "1d" case - ret.expires = System.currentTimeMillis() + 86400000; // 24*60*60*1000 - } else { - Log.e(LOGTAG, "exception in parseHeaders for " - + "max-age:" - + controls[i].substring(separator + 1)); - ret.expires = 0; - } - } - } - } - } - } - - // According to RFC 2616 section 14.32: - // HTTP/1.1 caches SHOULD treat "Pragma: no-cache" as if the - // client had sent "Cache-Control: no-cache" - if (NO_CACHE.equals(headers.getPragma())) { - ret.expires = 0; - } - - // According to RFC 2616 section 13.2.4, if an expiration has not been - // explicitly defined a heuristic to set an expiration may be used. - if (ret.expires == -1) { - if (ret.httpStatusCode == 301) { - // If it is a permanent redirect, and it did not have an - // explicit cache directive, then it never expires - ret.expires = Long.MAX_VALUE; - } else if (ret.httpStatusCode == 302 || ret.httpStatusCode == 307) { - // If it is temporary redirect, expires - ret.expires = 0; - } else if (ret.lastModified == null) { - // When we have no last-modified, then expire the content with - // in 24hrs as, according to the RFC, longer time requires a - // warning 113 to be added to the response. - - // Only add the default expiration for non-html markup. Some - // sites like news.google.com have no cache directives. - if (!mimeType.startsWith("text/html")) { - ret.expires = System.currentTimeMillis() + 86400000; // 24*60*60*1000 - } else { - // Setting a expires as zero will cache the result for - // forward/back nav. - ret.expires = 0; - } - } else { - // If we have a last-modified value, we could use it to set the - // expiration. Suggestion from RFC is 10% of time since - // last-modified. As we are on mobile, loads are expensive, - // increasing this to 20%. - - // 24 * 60 * 60 * 1000 - long lastmod = System.currentTimeMillis() + 86400000; - try { - lastmod = HttpDateTime.parse(ret.lastModified); - } catch (IllegalArgumentException ex) { - Log.e(LOGTAG, "illegal lastModified: " + ret.lastModified); - } - long difference = System.currentTimeMillis() - lastmod; - if (difference > 0) { - ret.expires = System.currentTimeMillis() + difference / 5; - } else { - // last modified is in the future, expire the content - // on the last modified - ret.expires = lastmod; - } - } - } - - return ret; - } -} diff --git a/core/java/android/webkit/CallbackProxy.java b/core/java/android/webkit/CallbackProxy.java deleted file mode 100644 index 4f8e5e4..0000000 --- a/core/java/android/webkit/CallbackProxy.java +++ /dev/null @@ -1,1018 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.webkit; - -import android.app.AlertDialog; -import android.content.ActivityNotFoundException; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.graphics.Bitmap; -import android.net.Uri; -import android.net.http.SslCertificate; -import android.net.http.SslError; -import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.os.SystemClock; -import android.util.Config; -import android.util.Log; -import android.view.KeyEvent; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.EditText; -import android.widget.TextView; -import com.android.internal.R; - -import java.net.MalformedURLException; -import java.net.URL; -import java.util.HashMap; - -/** - * This class is a proxy class for handling WebCore -> UI thread messaging. All - * the callback functions are called from the WebCore thread and messages are - * posted to the UI thread for the actual client callback. - */ -/* - * This class is created in the UI thread so its handler and any private classes - * that extend Handler will operate in the UI thread. - */ -class CallbackProxy extends Handler { - // Logging tag - private static final String LOGTAG = "CallbackProxy"; - // Instance of WebViewClient that is the client callback. - private volatile WebViewClient mWebViewClient; - // Instance of WebChromeClient for handling all chrome functions. - private volatile WebChromeClient mWebChromeClient; - // Instance of WebView for handling UI requests. - private final WebView mWebView; - // Client registered callback listener for download events - private volatile DownloadListener mDownloadListener; - // Keep track of multiple progress updates. - private boolean mProgressUpdatePending; - // Keep track of the last progress amount. - private volatile int mLatestProgress; - // Back/Forward list - private final WebBackForwardList mBackForwardList; - // Used to call startActivity during url override. - private final Context mContext; - - // Message Ids - private static final int PAGE_STARTED = 100; - private static final int RECEIVED_ICON = 101; - private static final int RECEIVED_TITLE = 102; - private static final int OVERRIDE_URL = 103; - private static final int AUTH_REQUEST = 104; - private static final int SSL_ERROR = 105; - private static final int PROGRESS = 106; - private static final int UPDATE_VISITED = 107; - private static final int LOAD_RESOURCE = 108; - private static final int CREATE_WINDOW = 109; - private static final int CLOSE_WINDOW = 110; - private static final int SAVE_PASSWORD = 111; - private static final int JS_ALERT = 112; - private static final int JS_CONFIRM = 113; - private static final int JS_PROMPT = 114; - private static final int JS_UNLOAD = 115; - private static final int ASYNC_KEYEVENTS = 116; - private static final int TOO_MANY_REDIRECTS = 117; - private static final int DOWNLOAD_FILE = 118; - private static final int REPORT_ERROR = 119; - private static final int RESEND_POST_DATA = 120; - private static final int PAGE_FINISHED = 121; - private static final int REQUEST_FOCUS = 122; - private static final int SCALE_CHANGED = 123; - private static final int RECEIVED_CERTIFICATE = 124; - private static final int SWITCH_OUT_HISTORY = 125; - - // Message triggered by the client to resume execution - private static final int NOTIFY = 200; - - // Result transportation object for returning results across thread - // boundaries. - private class ResultTransport<E> { - // Private result object - private E mResult; - - public synchronized void setResult(E result) { - mResult = result; - } - - public synchronized E getResult() { - return mResult; - } - } - - /** - * Construct a new CallbackProxy. - */ - public CallbackProxy(Context context, WebView w) { - // Used to start a default activity. - mContext = context; - mWebView = w; - mBackForwardList = new WebBackForwardList(); - } - - /** - * Set the WebViewClient. - * @param client An implementation of WebViewClient. - */ - public void setWebViewClient(WebViewClient client) { - mWebViewClient = client; - } - - /** - * Set the WebChromeClient. - * @param client An implementation of WebChromeClient. - */ - public void setWebChromeClient(WebChromeClient client) { - mWebChromeClient = client; - } - - /** - * Set the client DownloadListener. - * @param client An implementation of DownloadListener. - */ - public void setDownloadListener(DownloadListener client) { - mDownloadListener = client; - } - - /** - * Get the Back/Forward list to return to the user or to update the cached - * history list. - */ - public WebBackForwardList getBackForwardList() { - return mBackForwardList; - } - - /** - * Called by the UI side. Calling overrideUrlLoading from the WebCore - * side will post a message to call this method. - */ - public boolean uiOverrideUrlLoading(String overrideUrl) { - if (overrideUrl == null || overrideUrl.length() == 0) { - return false; - } - boolean override = false; - if (mWebViewClient != null) { - override = mWebViewClient.shouldOverrideUrlLoading(mWebView, - overrideUrl); - } else { - Intent intent = new Intent(Intent.ACTION_VIEW, - Uri.parse(overrideUrl)); - intent.addCategory(Intent.CATEGORY_BROWSABLE); - try { - mContext.startActivity(intent); - override = true; - } catch (ActivityNotFoundException ex) { - // If no application can handle the URL, assume that the - // browser can handle it. - } - } - return override; - } - - /** - * Called by UI side. - */ - public boolean uiOverrideKeyEvent(KeyEvent event) { - if (mWebViewClient != null) { - return mWebViewClient.shouldOverrideKeyEvent(mWebView, event); - } - return false; - } - - @Override - public void handleMessage(Message msg) { - // We don't have to do synchronization because this function operates - // in the UI thread. The WebViewClient and WebChromeClient functions - // that check for a non-null callback are ok because java ensures atomic - // 32-bit reads and writes. - switch (msg.what) { - case PAGE_STARTED: - if (mWebViewClient != null) { - mWebViewClient.onPageStarted(mWebView, - msg.getData().getString("url"), - (Bitmap) msg.obj); - } - break; - - case PAGE_FINISHED: - if (mWebViewClient != null) { - mWebViewClient.onPageFinished(mWebView, (String) msg.obj); - } - break; - - case RECEIVED_ICON: - if (mWebChromeClient != null) { - mWebChromeClient.onReceivedIcon(mWebView, (Bitmap) msg.obj); - } - break; - - case RECEIVED_TITLE: - if (mWebChromeClient != null) { - mWebChromeClient.onReceivedTitle(mWebView, - (String) msg.obj); - } - break; - - case TOO_MANY_REDIRECTS: - Message cancelMsg = - (Message) msg.getData().getParcelable("cancelMsg"); - Message continueMsg = - (Message) msg.getData().getParcelable("continueMsg"); - if (mWebViewClient != null) { - mWebViewClient.onTooManyRedirects(mWebView, cancelMsg, - continueMsg); - } else { - cancelMsg.sendToTarget(); - } - break; - - case REPORT_ERROR: - if (mWebViewClient != null) { - int reasonCode = msg.arg1; - final String description = msg.getData().getString("description"); - final String failUrl = msg.getData().getString("failingUrl"); - mWebViewClient.onReceivedError(mWebView, reasonCode, - description, failUrl); - } - break; - - case RESEND_POST_DATA: - Message resend = - (Message) msg.getData().getParcelable("resend"); - Message dontResend = - (Message) msg.getData().getParcelable("dontResend"); - if (mWebViewClient != null) { - mWebViewClient.onFormResubmission(mWebView, dontResend, - resend); - } else { - dontResend.sendToTarget(); - } - break; - - case OVERRIDE_URL: - String overrideUrl = msg.getData().getString("url"); - boolean override = uiOverrideUrlLoading(overrideUrl); - ResultTransport<Boolean> result = - (ResultTransport<Boolean>) msg.obj; - synchronized (this) { - result.setResult(override); - notify(); - } - break; - - case AUTH_REQUEST: - if (mWebViewClient != null) { - HttpAuthHandler handler = (HttpAuthHandler) msg.obj; - String host = msg.getData().getString("host"); - String realm = msg.getData().getString("realm"); - mWebViewClient.onReceivedHttpAuthRequest(mWebView, handler, - host, realm); - } - break; - - case SSL_ERROR: - if (mWebViewClient != null) { - HashMap<String, Object> map = - (HashMap<String, Object>) msg.obj; - mWebViewClient.onReceivedSslError(mWebView, - (SslErrorHandler) map.get("handler"), - (SslError) map.get("error")); - } - break; - - case PROGRESS: - // Synchronize to ensure mLatestProgress is not modified after - // setProgress is called and before mProgressUpdatePending is - // changed. - synchronized (this) { - if (mWebChromeClient != null) { - mWebChromeClient.onProgressChanged(mWebView, - mLatestProgress); - } - mProgressUpdatePending = false; - } - break; - - case UPDATE_VISITED: - if (mWebViewClient != null) { - mWebViewClient.doUpdateVisitedHistory(mWebView, - (String) msg.obj, msg.arg1 != 0); - } - break; - - case LOAD_RESOURCE: - if (mWebViewClient != null) { - mWebViewClient.onLoadResource(mWebView, (String) msg.obj); - } - break; - - case DOWNLOAD_FILE: - if (mDownloadListener != null) { - String url = msg.getData().getString("url"); - String userAgent = msg.getData().getString("userAgent"); - String contentDisposition = - msg.getData().getString("contentDisposition"); - String mimetype = msg.getData().getString("mimetype"); - Long contentLength = msg.getData().getLong("contentLength"); - - mDownloadListener.onDownloadStart(url, userAgent, - contentDisposition, mimetype, contentLength); - } - break; - - case CREATE_WINDOW: - if (mWebChromeClient != null) { - if (!mWebChromeClient.onCreateWindow(mWebView, - msg.arg1 == 1, msg.arg2 == 1, - (Message) msg.obj)) { - synchronized (this) { - notify(); - } - } - } - break; - - case REQUEST_FOCUS: - if (mWebChromeClient != null) { - mWebChromeClient.onRequestFocus(mWebView); - } - break; - - case CLOSE_WINDOW: - if (mWebChromeClient != null) { - mWebChromeClient.onCloseWindow((WebView) msg.obj); - } - break; - - case SAVE_PASSWORD: - Bundle bundle = msg.getData(); - 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(schemePlusHost, username, password, - (Message) msg.obj)) { - synchronized (this) { - notify(); - } - } - break; - - case ASYNC_KEYEVENTS: - if (mWebViewClient != null) { - mWebViewClient.onUnhandledKeyEvent(mWebView, - (KeyEvent) msg.obj); - } - break; - - case JS_ALERT: - if (mWebChromeClient != null) { - final JsResult res = (JsResult) msg.obj; - String message = msg.getData().getString("message"); - String url = msg.getData().getString("url"); - if (!mWebChromeClient.onJsAlert(mWebView, url, message, - res)) { - new AlertDialog.Builder(mContext) - .setTitle(getJsDialogTitle(url)) - .setMessage(message) - .setPositiveButton(R.string.ok, - new AlertDialog.OnClickListener() { - public void onClick( - DialogInterface dialog, - int which) { - res.confirm(); - } - }) - .setCancelable(false) - .show(); - } - res.setReady(); - } - break; - - case JS_CONFIRM: - if (mWebChromeClient != null) { - final JsResult res = (JsResult) msg.obj; - String message = msg.getData().getString("message"); - String url = msg.getData().getString("url"); - if (!mWebChromeClient.onJsConfirm(mWebView, url, message, - res)) { - new AlertDialog.Builder(mContext) - .setTitle(getJsDialogTitle(url)) - .setMessage(message) - .setPositiveButton(R.string.ok, - new DialogInterface.OnClickListener() { - public void onClick( - DialogInterface dialog, - int which) { - res.confirm(); - }}) - .setNegativeButton(R.string.cancel, - new DialogInterface.OnClickListener() { - public void onClick( - DialogInterface dialog, - int which) { - res.cancel(); - }}) - .show(); - } - // Tell the JsResult that it is ready for client - // interaction. - res.setReady(); - } - break; - - case JS_PROMPT: - if (mWebChromeClient != null) { - final JsPromptResult res = (JsPromptResult) msg.obj; - String message = msg.getData().getString("message"); - String defaultVal = msg.getData().getString("default"); - String url = msg.getData().getString("url"); - if (!mWebChromeClient.onJsPrompt(mWebView, url, message, - defaultVal, res)) { - final LayoutInflater factory = LayoutInflater - .from(mContext); - final View view = factory.inflate(R.layout.js_prompt, - null); - final EditText v = (EditText) view - .findViewById(R.id.value); - v.setText(defaultVal); - ((TextView) view.findViewById(R.id.message)) - .setText(message); - new AlertDialog.Builder(mContext) - .setTitle(getJsDialogTitle(url)) - .setView(view) - .setPositiveButton(R.string.ok, - new DialogInterface.OnClickListener() { - public void onClick( - DialogInterface dialog, - int whichButton) { - res.confirm(v.getText() - .toString()); - } - }) - .setNegativeButton(R.string.cancel, - new DialogInterface.OnClickListener() { - public void onClick( - DialogInterface dialog, - int whichButton) { - res.cancel(); - } - }) - .setOnCancelListener( - new DialogInterface.OnCancelListener() { - public void onCancel( - DialogInterface dialog) { - res.cancel(); - } - }) - .show(); - } - // Tell the JsResult that it is ready for client - // interaction. - res.setReady(); - } - break; - - case JS_UNLOAD: - if (mWebChromeClient != null) { - final JsResult res = (JsResult) msg.obj; - String message = msg.getData().getString("message"); - String url = msg.getData().getString("url"); - if (!mWebChromeClient.onJsBeforeUnload(mWebView, url, - message, res)) { - final String m = mContext.getString( - R.string.js_dialog_before_unload, message); - new AlertDialog.Builder(mContext) - .setMessage(m) - .setPositiveButton(R.string.ok, - new DialogInterface.OnClickListener() { - public void onClick( - DialogInterface dialog, - int which) { - res.confirm(); - } - }) - .setNegativeButton(R.string.cancel, - new DialogInterface.OnClickListener() { - public void onClick( - DialogInterface dialog, - int which) { - res.cancel(); - } - }) - .show(); - } - res.setReady(); - } - break; - - case RECEIVED_CERTIFICATE: - mWebView.setCertificate((SslCertificate) msg.obj); - break; - - case NOTIFY: - synchronized (this) { - notify(); - } - break; - - case SCALE_CHANGED: - if (mWebViewClient != null) { - mWebViewClient.onScaleChanged(mWebView, msg.getData() - .getFloat("old"), msg.getData().getFloat("new")); - } - break; - - case SWITCH_OUT_HISTORY: - mWebView.switchOutDrawHistory(); - break; - } - } - - /** - * Return the latest progress. - */ - public int getProgress() { - return mLatestProgress; - } - - /** - * Called by WebCore side to switch out of history Picture drawing mode - */ - void switchOutDrawHistory() { - sendMessage(obtainMessage(SWITCH_OUT_HISTORY)); - } - - private String getJsDialogTitle(String url) { - String title = url; - if (URLUtil.isDataUrl(url)) { - // For data: urls, we just display 'JavaScript' similar to Safari. - title = mContext.getString(R.string.js_dialog_title_default); - } else { - try { - URL aUrl = new URL(url); - // For example: "The page at 'http://www.mit.edu' says:" - title = mContext.getString(R.string.js_dialog_title, - aUrl.getProtocol() + "://" + aUrl.getHost()); - } catch (MalformedURLException ex) { - // do nothing. just use the url as the title - } - } - return title; - } - - //-------------------------------------------------------------------------- - // WebViewClient functions. - // NOTE: shouldOverrideKeyEvent is never called from the WebCore thread so - // it is not necessary to include it here. - //-------------------------------------------------------------------------- - - // Performance probe - private long mWebCoreThreadTime; - - public void onPageStarted(String url, Bitmap favicon) { - // Do an unsynchronized quick check to avoid posting if no callback has - // been set. - if (mWebViewClient == null) { - return; - } - // Performance probe - if (false) { - mWebCoreThreadTime = SystemClock.currentThreadTimeMillis(); - Network.getInstance(mContext).startTiming(); - } - Message msg = obtainMessage(PAGE_STARTED); - msg.obj = favicon; - msg.getData().putString("url", url); - sendMessage(msg); - } - - public void onPageFinished(String url) { - // Do an unsynchronized quick check to avoid posting if no callback has - // been set. - if (mWebViewClient == null) { - return; - } - // Performance probe - if (false) { - Log.d("WebCore", "WebCore thread used " + - (SystemClock.currentThreadTimeMillis() - mWebCoreThreadTime) - + " ms"); - Network.getInstance(mContext).stopTiming(); - } - Message msg = obtainMessage(PAGE_FINISHED, url); - sendMessage(msg); - } - - public void onTooManyRedirects(Message cancelMsg, Message continueMsg) { - // Do an unsynchronized quick check to avoid posting if no callback has - // been set. - if (mWebViewClient == null) { - cancelMsg.sendToTarget(); - return; - } - - Message msg = obtainMessage(TOO_MANY_REDIRECTS); - Bundle bundle = msg.getData(); - bundle.putParcelable("cancelMsg", cancelMsg); - bundle.putParcelable("continueMsg", continueMsg); - sendMessage(msg); - } - - public void onReceivedError(int errorCode, String description, - String failingUrl) { - // Do an unsynchronized quick check to avoid posting if no callback has - // been set. - if (mWebViewClient == null) { - return; - } - - Message msg = obtainMessage(REPORT_ERROR); - msg.arg1 = errorCode; - msg.getData().putString("description", description); - msg.getData().putString("failingUrl", failingUrl); - sendMessage(msg); - } - - public void onFormResubmission(Message dontResend, - Message resend) { - // Do an unsynchronized quick check to avoid posting if no callback has - // been set. - if (mWebViewClient == null) { - dontResend.sendToTarget(); - return; - } - - Message msg = obtainMessage(RESEND_POST_DATA); - Bundle bundle = msg.getData(); - bundle.putParcelable("resend", resend); - bundle.putParcelable("dontResend", dontResend); - sendMessage(msg); - } - - /** - * Called by the WebCore side - */ - public boolean shouldOverrideUrlLoading(String url) { - // We have a default behavior if no client exists so always send the - // message. - ResultTransport<Boolean> res = new ResultTransport<Boolean>(); - Message msg = obtainMessage(OVERRIDE_URL); - msg.getData().putString("url", url); - msg.obj = res; - synchronized (this) { - sendMessage(msg); - try { - wait(); - } catch (InterruptedException e) { - Log.e(LOGTAG, "Caught exception while waiting for overrideUrl"); - Log.e(LOGTAG, Log.getStackTraceString(e)); - } - } - return res.getResult().booleanValue(); - } - - public void onReceivedHttpAuthRequest(HttpAuthHandler handler, - String hostName, String realmName) { - // Do an unsynchronized quick check to avoid posting if no callback has - // been set. - if (mWebViewClient == null) { - handler.cancel(); - return; - } - Message msg = obtainMessage(AUTH_REQUEST, handler); - msg.getData().putString("host", hostName); - msg.getData().putString("realm", realmName); - sendMessage(msg); - } - /** - * @hide - hide this because it contains a parameter of type SslError. - * SslError is located in a hidden package. - */ - public void onReceivedSslError(SslErrorHandler handler, SslError error) { - // Do an unsynchronized quick check to avoid posting if no callback has - // been set. - if (mWebViewClient == null) { - handler.cancel(); - return; - } - Message msg = obtainMessage(SSL_ERROR); - //, handler); - HashMap<String, Object> map = new HashMap(); - map.put("handler", handler); - map.put("error", error); - msg.obj = map; - sendMessage(msg); - } - /** - * @hide - hide this because it contains a parameter of type SslCertificate, - * which is located in a hidden package. - */ - - public void onReceivedCertificate(SslCertificate certificate) { - // Do an unsynchronized quick check to avoid posting if no callback has - // been set. - if (mWebViewClient == null) { - return; - } - // here, certificate can be null (if the site is not secure) - sendMessage(obtainMessage(RECEIVED_CERTIFICATE, certificate)); - } - - public void doUpdateVisitedHistory(String url, boolean isReload) { - // Do an unsynchronized quick check to avoid posting if no callback has - // been set. - if (mWebViewClient == null) { - return; - } - sendMessage(obtainMessage(UPDATE_VISITED, isReload ? 1 : 0, 0, url)); - } - - public void onLoadResource(String url) { - // Do an unsynchronized quick check to avoid posting if no callback has - // been set. - if (mWebViewClient == null) { - return; - } - sendMessage(obtainMessage(LOAD_RESOURCE, url)); - } - - public void onUnhandledKeyEvent(KeyEvent event) { - // Do an unsynchronized quick check to avoid posting if no callback has - // been set. - if (mWebViewClient == null) { - return; - } - sendMessage(obtainMessage(ASYNC_KEYEVENTS, event)); - } - - public void onScaleChanged(float oldScale, float newScale) { - // Do an unsynchronized quick check to avoid posting if no callback has - // been set. - if (mWebViewClient == null) { - return; - } - Message msg = obtainMessage(SCALE_CHANGED); - Bundle bundle = msg.getData(); - bundle.putFloat("old", oldScale); - bundle.putFloat("new", newScale); - sendMessage(msg); - } - - //-------------------------------------------------------------------------- - // DownloadListener functions. - //-------------------------------------------------------------------------- - - /** - * Starts a download if a download listener has been registered, otherwise - * return false. - */ - public boolean onDownloadStart(String url, String userAgent, - String contentDisposition, String mimetype, long contentLength) { - // Do an unsynchronized quick check to avoid posting if no callback has - // been set. - if (mDownloadListener == null) { - // Cancel the download if there is no browser client. - return false; - } - - Message msg = obtainMessage(DOWNLOAD_FILE); - Bundle bundle = msg.getData(); - bundle.putString("url", url); - bundle.putString("userAgent", userAgent); - bundle.putString("mimetype", mimetype); - bundle.putLong("contentLength", contentLength); - bundle.putString("contentDisposition", contentDisposition); - sendMessage(msg); - return true; - } - - - //-------------------------------------------------------------------------- - // WebView specific functions that do not interact with a client. These - // functions just need to operate within the UI thread. - //-------------------------------------------------------------------------- - - 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) { - junit.framework.Assert.assertNull(resumeMsg); - } - resumeMsg = obtainMessage(NOTIFY); - - Message msg = obtainMessage(SAVE_PASSWORD, resumeMsg); - Bundle bundle = msg.getData(); - bundle.putString("host", schemePlusHost); - bundle.putString("username", username); - bundle.putString("password", password); - synchronized (this) { - sendMessage(msg); - try { - wait(); - } catch (InterruptedException e) { - Log.e(LOGTAG, - "Caught exception while waiting for onSavePassword"); - Log.e(LOGTAG, Log.getStackTraceString(e)); - } - } - // Doesn't matter here - return false; - } - - //-------------------------------------------------------------------------- - // WebChromeClient methods - //-------------------------------------------------------------------------- - - public void onProgressChanged(int newProgress) { - // Synchronize so that mLatestProgress is up-to-date. - synchronized (this) { - mLatestProgress = newProgress; - if (mWebChromeClient == null) { - return; - } - if (!mProgressUpdatePending) { - sendEmptyMessage(PROGRESS); - mProgressUpdatePending = true; - } - } - } - - public WebView createWindow(boolean dialog, boolean userGesture) { - // Do an unsynchronized quick check to avoid posting if no callback has - // been set. - if (mWebChromeClient == null) { - return null; - } - - WebView.WebViewTransport transport = mWebView.new WebViewTransport(); - final Message msg = obtainMessage(NOTIFY); - msg.obj = transport; - synchronized (this) { - sendMessage(obtainMessage(CREATE_WINDOW, dialog ? 1 : 0, - userGesture ? 1 : 0, msg)); - try { - wait(); - } catch (InterruptedException e) { - Log.e(LOGTAG, - "Caught exception while waiting for createWindow"); - Log.e(LOGTAG, Log.getStackTraceString(e)); - } - } - - WebView w = transport.getWebView(); - if (w != null) { - w.getWebViewCore().initializeSubwindow(); - } - return w; - } - - public void onRequestFocus() { - // Do an unsynchronized quick check to avoid posting if no callback has - // been set. - if (mWebChromeClient == null) { - return; - } - - sendEmptyMessage(REQUEST_FOCUS); - } - - public void onCloseWindow(WebView window) { - // Do an unsynchronized quick check to avoid posting if no callback has - // been set. - if (mWebChromeClient == null) { - return; - } - sendMessage(obtainMessage(CLOSE_WINDOW, window)); - } - - public void onReceivedIcon(Bitmap icon) { - if (Config.DEBUG && mBackForwardList.getCurrentItem() == null) { - throw new AssertionError(); - } - mBackForwardList.getCurrentItem().setFavicon(icon); - // Do an unsynchronized quick check to avoid posting if no callback has - // been set. - if (mWebChromeClient == null) { - return; - } - sendMessage(obtainMessage(RECEIVED_ICON, icon)); - } - - public void onReceivedTitle(String title) { - // Do an unsynchronized quick check to avoid posting if no callback has - // been set. - if (mWebChromeClient == null) { - return; - } - sendMessage(obtainMessage(RECEIVED_TITLE, title)); - } - - public void onJsAlert(String url, String message) { - // Do an unsynchronized quick check to avoid posting if no callback has - // been set. - if (mWebChromeClient == null) { - return; - } - JsResult result = new JsResult(this, false); - Message alert = obtainMessage(JS_ALERT, result); - alert.getData().putString("message", message); - alert.getData().putString("url", url); - synchronized (this) { - sendMessage(alert); - try { - wait(); - } catch (InterruptedException e) { - Log.e(LOGTAG, "Caught exception while waiting for jsAlert"); - Log.e(LOGTAG, Log.getStackTraceString(e)); - } - } - } - - public boolean onJsConfirm(String url, String message) { - // Do an unsynchronized quick check to avoid posting if no callback has - // been set. - if (mWebChromeClient == null) { - return false; - } - JsResult result = new JsResult(this, false); - Message confirm = obtainMessage(JS_CONFIRM, result); - confirm.getData().putString("message", message); - confirm.getData().putString("url", url); - synchronized (this) { - sendMessage(confirm); - try { - wait(); - } catch (InterruptedException e) { - Log.e(LOGTAG, "Caught exception while waiting for jsConfirm"); - Log.e(LOGTAG, Log.getStackTraceString(e)); - } - } - return result.getResult(); - } - - public String onJsPrompt(String url, String message, String defaultValue) { - // Do an unsynchronized quick check to avoid posting if no callback has - // been set. - if (mWebChromeClient == null) { - return null; - } - JsPromptResult result = new JsPromptResult(this); - Message prompt = obtainMessage(JS_PROMPT, result); - prompt.getData().putString("message", message); - prompt.getData().putString("default", defaultValue); - prompt.getData().putString("url", url); - synchronized (this) { - sendMessage(prompt); - try { - wait(); - } catch (InterruptedException e) { - Log.e(LOGTAG, "Caught exception while waiting for jsPrompt"); - Log.e(LOGTAG, Log.getStackTraceString(e)); - } - } - return result.getStringResult(); - } - - public boolean onJsBeforeUnload(String url, String message) { - // Do an unsynchronized quick check to avoid posting if no callback has - // been set. - if (mWebChromeClient == null) { - return true; - } - JsResult result = new JsResult(this, true); - Message confirm = obtainMessage(JS_UNLOAD, result); - confirm.getData().putString("message", message); - confirm.getData().putString("url", url); - synchronized (this) { - sendMessage(confirm); - try { - wait(); - } catch (InterruptedException e) { - Log.e(LOGTAG, "Caught exception while waiting for jsUnload"); - Log.e(LOGTAG, Log.getStackTraceString(e)); - } - } - return result.getResult(); - } -} diff --git a/core/java/android/webkit/ContentLoader.java b/core/java/android/webkit/ContentLoader.java deleted file mode 100644 index fb01c8c..0000000 --- a/core/java/android/webkit/ContentLoader.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.webkit; - -import android.content.Context; -import android.net.http.EventHandler; -import android.net.http.Headers; -import android.net.Uri; - -import java.io.File; -import java.io.FileInputStream; - -/** - * This class is a concrete implementation of StreamLoader that loads - * "content:" URIs - */ -class ContentLoader extends StreamLoader { - - private String mUrl; - private Context mContext; - private String mContentType; - - /** - * Construct a ContentLoader with the specified content URI - * - * @param rawUrl "content:" url pointing to content to be loaded. This url - * is the same url passed in to the WebView. - * @param loadListener LoadListener to pass the content to - * @param context Context to use to access the asset. - */ - ContentLoader(String rawUrl, LoadListener loadListener, Context context) { - super(loadListener); - mContext = context; - - /* strip off mimetype */ - int mimeIndex = rawUrl.lastIndexOf('?'); - if (mimeIndex != -1) { - mUrl = rawUrl.substring(0, mimeIndex); - mContentType = rawUrl.substring(mimeIndex + 1); - } else { - mUrl = rawUrl; - } - - } - - @Override - protected boolean setupStreamAndSendStatus() { - Uri uri = Uri.parse(mUrl); - if (uri == null) { - mHandler.error( - EventHandler.FILE_NOT_FOUND_ERROR, - mContext.getString( - com.android.internal.R.string.httpErrorBadUrl) + - " " + mUrl); - return false; - } - - try { - mDataStream = mContext.getContentResolver().openInputStream(uri); - 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) + - " " + ex.getMessage()); - return false; - - } catch (java.io.IOException ex) { - mHandler.error( - EventHandler.FILE_ERROR, - mContext.getString( - com.android.internal.R.string.httpErrorFileNotFound) + - " " + ex.getMessage()); - return false; - } catch (RuntimeException ex) { - // readExceptionWithFileNotFoundExceptionFromParcel in DatabaseUtils - // can throw a serial of RuntimeException. Catch them all here. - mHandler.error( - EventHandler.FILE_ERROR, - mContext.getString( - com.android.internal.R.string.httpErrorFileNotFound) + - " " + ex.getMessage()); - return false; - } - return true; - } - - @Override - protected void buildHeaders(Headers headers) { - if (mContentType != null) { - headers.setContentType("text/html"); - } - } - - /** - * Construct a ContentLoader and instruct it to start loading. - * - * @param url "content:" url pointing to content to be loaded - * @param loadListener LoadListener to pass the content to - * @param context Context to use to access the asset. - */ - public static void requestUrl(String url, LoadListener loadListener, - Context context) { - ContentLoader loader = new ContentLoader(url, loadListener, context); - loader.load(); - } - -} diff --git a/core/java/android/webkit/CookieManager.java b/core/java/android/webkit/CookieManager.java deleted file mode 100644 index 07c1a5d..0000000 --- a/core/java/android/webkit/CookieManager.java +++ /dev/null @@ -1,934 +0,0 @@ -/* - * Copyright (C) 2006 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.webkit; - -import android.net.ParseException; -import android.net.WebAddress; -import android.util.Config; -import android.util.Log; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.Map; - -/** - * CookieManager manages cookies according to RFC2109 spec. - */ -public final class CookieManager { - - private static CookieManager sRef; - - private static final String LOGTAG = "webkit"; - - private static final String DOMAIN = "domain"; - - private static final String PATH = "path"; - - private static final String EXPIRES = "expires"; - - private static final String SECURE = "secure"; - - private static final String MAX_AGE = "max-age"; - - private static final String HTTP_ONLY = "httponly"; - - private static final String HTTPS = "https"; - - private static final char PERIOD = '.'; - - private static final char COMMA = ','; - - private static final char SEMICOLON = ';'; - - private static final char EQUAL = '='; - - private static final char PATH_DELIM = '/'; - - private static final char QUESTION_MARK = '?'; - - private static final char WHITE_SPACE = ' '; - - private static final char QUOTATION = '\"'; - - private static final int SECURE_LENGTH = SECURE.length(); - - private static final int HTTP_ONLY_LENGTH = HTTP_ONLY.length(); - - // RFC2109 defines 4k as maximum size of a cookie - private static final int MAX_COOKIE_LENGTH = 4 * 1024; - - // RFC2109 defines 20 as max cookie count per domain. As we track with base - // domain, we allow 50 per base domain - private static final int MAX_COOKIE_COUNT_PER_BASE_DOMAIN = 50; - - // RFC2109 defines 300 as max count of domains. As we track with base - // domain, we set 200 as max base domain count - private static final int MAX_DOMAIN_COUNT = 200; - - // max cookie count to limit RAM cookie takes less than 100k, it is based on - // average cookie entry size is less than 100 bytes - private static final int MAX_RAM_COOKIES_COUNT = 1000; - - // max domain count to limit RAM cookie takes less than 100k, - private static final int MAX_RAM_DOMAIN_COUNT = 15; - - private Map<String, ArrayList<Cookie>> mCookieMap = new LinkedHashMap - <String, ArrayList<Cookie>>(MAX_DOMAIN_COUNT, 0.75f, true); - - private boolean mAcceptCookie = true; - - /** - * This contains a list of 2nd-level domains that aren't allowed to have - * wildcards when combined with country-codes. For example: [.co.uk]. - */ - private final static String[] BAD_COUNTRY_2LDS = - { "ac", "co", "com", "ed", "edu", "go", "gouv", "gov", "info", - "lg", "ne", "net", "or", "org" }; - - static { - Arrays.sort(BAD_COUNTRY_2LDS); - } - - /** - * Package level class to be accessed by cookie sync manager - */ - static class Cookie { - static final byte MODE_NEW = 0; - - static final byte MODE_NORMAL = 1; - - static final byte MODE_DELETED = 2; - - static final byte MODE_REPLACED = 3; - - String domain; - - String path; - - String name; - - String value; - - long expires; - - long lastAcessTime; - - long lastUpdateTime; - - boolean secure; - - byte mode; - - Cookie() { - } - - Cookie(String defaultDomain, String defaultPath) { - domain = defaultDomain; - path = defaultPath; - expires = -1; - } - - boolean exactMatch(Cookie in) { - return domain.equals(in.domain) && path.equals(in.path) && - name.equals(in.name); - } - - boolean domainMatch(String urlHost) { - if (domain.startsWith(".")) { - if (urlHost.endsWith(domain.substring(1))) { - int len = domain.length(); - int urlLen = urlHost.length(); - if (urlLen > len - 1) { - // make sure bar.com doesn't match .ar.com - return urlHost.charAt(urlLen - len) == PERIOD; - } - return true; - } - return false; - } else { - // exact match if domain is not leading w/ dot - return urlHost.equals(domain); - } - } - - boolean pathMatch(String urlPath) { - if (urlPath.startsWith(path)) { - int len = path.length(); - if (len == 0) { - Log.w(LOGTAG, "Empty cookie path"); - return false; - } - int urlLen = urlPath.length(); - if (path.charAt(len-1) != PATH_DELIM && urlLen > len) { - // make sure /wee doesn't match /we - return urlPath.charAt(len) == PATH_DELIM; - } - return true; - } - return false; - } - - public String toString() { - return "domain: " + domain + "; path: " + path + "; name: " + name - + "; value: " + value; - } - } - - private CookieManager() { - } - - protected Object clone() throws CloneNotSupportedException { - throw new CloneNotSupportedException("doesn't implement Cloneable"); - } - - /** - * Get a singleton CookieManager. If this is called before any - * {@link WebView} is created or outside of {@link WebView} context, the - * caller needs to call {@link CookieSyncManager#createInstance(Context)} - * first. - * - * @return CookieManager -= */ - public static synchronized CookieManager getInstance() { - if (sRef == null) { - sRef = new CookieManager(); - } - return sRef; - } - - /** - * Control whether cookie is enabled or disabled - * @param accept TRUE if accept cookie - */ - public synchronized void setAcceptCookie(boolean accept) { - mAcceptCookie = accept; - } - - /** - * Return whether cookie is enabled - * @return TRUE if accept cookie - */ - public synchronized boolean acceptCookie() { - return mAcceptCookie; - } - - /** - * Set cookie for a given url. The old cookie with same host/path/name will - * be removed. The new cookie will be added if it is not expired or it does - * not have expiration which implies it is session cookie. - * @param url The url which cookie is set for - * @param value The value for set-cookie: in http response header - */ - public void setCookie(String url, String value) { - WebAddress uri; - try { - uri = new WebAddress(url); - } catch (ParseException ex) { - Log.e(LOGTAG, "Bad address: " + url); - return; - } - setCookie(uri, value); - } - - /** - * Set cookie for a given uri. The old cookie with same host/path/name will - * be removed. The new cookie will be added if it is not expired or it does - * not have expiration which implies it is session cookie. - * @param uri The uri which cookie is set for - * @param value The value for set-cookie: in http response header - * @hide - hide this because it takes in a parameter of type WebAddress, - * a system private class. - */ - public synchronized void setCookie(WebAddress uri, String value) { - if (value != null && value.length() > MAX_COOKIE_LENGTH) { - return; - } - if (!mAcceptCookie || uri == null) { - return; - } - if (Config.LOGV) { - Log.v(LOGTAG, "setCookie: uri: " + uri + " value: " + value); - } - - String[] hostAndPath = getHostAndPath(uri); - 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 { - cookies = parseCookie(hostAndPath[0], hostAndPath[1], value); - } catch (RuntimeException ex) { - Log.e(LOGTAG, "parse cookie failed for: " + value); - } - - if (cookies == null || cookies.size() == 0) { - return; - } - - String baseDomain = getBaseDomain(hostAndPath[0]); - ArrayList<Cookie> cookieList = mCookieMap.get(baseDomain); - if (cookieList == null) { - cookieList = CookieSyncManager.getInstance() - .getCookiesForDomain(baseDomain); - mCookieMap.put(baseDomain, cookieList); - } - - long now = System.currentTimeMillis(); - int size = cookies.size(); - for (int i = 0; i < size; i++) { - Cookie cookie = cookies.get(i); - - boolean done = false; - Iterator<Cookie> iter = cookieList.iterator(); - while (iter.hasNext()) { - Cookie cookieEntry = iter.next(); - if (cookie.exactMatch(cookieEntry)) { - // expires == -1 means no expires defined. Otherwise - // negative means far future - if (cookie.expires < 0 || cookie.expires > now) { - // secure cookies can't be overwritten by non-HTTPS url - if (!cookieEntry.secure || HTTPS.equals(uri.mScheme)) { - cookieEntry.value = cookie.value; - cookieEntry.expires = cookie.expires; - cookieEntry.secure = cookie.secure; - cookieEntry.lastAcessTime = now; - cookieEntry.lastUpdateTime = now; - cookieEntry.mode = Cookie.MODE_REPLACED; - } - } else { - cookieEntry.lastUpdateTime = now; - cookieEntry.mode = Cookie.MODE_DELETED; - } - done = true; - break; - } - } - - // expires == -1 means no expires defined. Otherwise negative means - // far future - if (!done && (cookie.expires < 0 || cookie.expires > now)) { - cookie.lastAcessTime = now; - cookie.lastUpdateTime = now; - cookie.mode = Cookie.MODE_NEW; - if (cookieList.size() > MAX_COOKIE_COUNT_PER_BASE_DOMAIN) { - Cookie toDelete = new Cookie(); - toDelete.lastAcessTime = now; - Iterator<Cookie> iter2 = cookieList.iterator(); - while (iter2.hasNext()) { - Cookie cookieEntry2 = iter2.next(); - if ((cookieEntry2.lastAcessTime < toDelete.lastAcessTime) - && cookieEntry2.mode != Cookie.MODE_DELETED) { - toDelete = cookieEntry2; - } - } - toDelete.mode = Cookie.MODE_DELETED; - } - cookieList.add(cookie); - } - } - } - - /** - * Get cookie(s) for a given url so that it can be set to "cookie:" in http - * request header. - * @param url The url needs cookie - * @return The cookies in the format of NAME=VALUE [; NAME=VALUE] - */ - public String getCookie(String url) { - WebAddress uri; - try { - uri = new WebAddress(url); - } catch (ParseException ex) { - Log.e(LOGTAG, "Bad address: " + url); - return null; - } - return getCookie(uri); - } - - /** - * Get cookie(s) for a given uri so that it can be set to "cookie:" in http - * request header. - * @param uri The uri needs cookie - * @return The cookies in the format of NAME=VALUE [; NAME=VALUE] - * @hide - hide this because it has a parameter of type WebAddress, which - * is a system private class. - */ - public synchronized String getCookie(WebAddress uri) { - if (!mAcceptCookie || uri == null) { - return null; - } - - String[] hostAndPath = getHostAndPath(uri); - if (hostAndPath == null) { - return null; - } - - String baseDomain = getBaseDomain(hostAndPath[0]); - ArrayList<Cookie> cookieList = mCookieMap.get(baseDomain); - if (cookieList == null) { - cookieList = CookieSyncManager.getInstance() - .getCookiesForDomain(baseDomain); - mCookieMap.put(baseDomain, cookieList); - } - - long now = System.currentTimeMillis(); - boolean secure = HTTPS.equals(uri.mScheme); - Iterator<Cookie> iter = cookieList.iterator(); - StringBuilder ret = new StringBuilder(256); - - while (iter.hasNext()) { - Cookie cookie = iter.next(); - if (cookie.domainMatch(hostAndPath[0]) && - cookie.pathMatch(hostAndPath[1]) - // expires == -1 means no expires defined. Otherwise - // negative means far future - && (cookie.expires < 0 || cookie.expires > now) - && (!cookie.secure || secure) - && cookie.mode != Cookie.MODE_DELETED) { - cookie.lastAcessTime = now; - - if (ret.length() > 0) { - ret.append(SEMICOLON); - // according to RC2109, SEMICOLON is office separator, - // but when log in yahoo.com, it needs WHITE_SPACE too. - ret.append(WHITE_SPACE); - } - - ret.append(cookie.name); - ret.append(EQUAL); - ret.append(cookie.value); - } - } - if (ret.length() > 0) { - if (Config.LOGV) { - Log.v(LOGTAG, "getCookie: uri: " + uri + " value: " + ret); - } - return ret.toString(); - } else { - if (Config.LOGV) { - Log.v(LOGTAG, "getCookie: uri: " + uri - + " But can't find cookie."); - } - return null; - } - } - - /** - * Remove all session cookies, which are cookies without expiration date - */ - public void removeSessionCookie() { - final Runnable clearCache = new Runnable() { - public void run() { - synchronized(CookieManager.this) { - Collection<ArrayList<Cookie>> cookieList = mCookieMap.values(); - Iterator<ArrayList<Cookie>> listIter = cookieList.iterator(); - while (listIter.hasNext()) { - ArrayList<Cookie> list = listIter.next(); - Iterator<Cookie> iter = list.iterator(); - while (iter.hasNext()) { - Cookie cookie = iter.next(); - if (cookie.expires == -1) { - iter.remove(); - } - } - } - CookieSyncManager.getInstance().clearSessionCookies(); - } - } - }; - new Thread(clearCache).start(); - } - - /** - * Remove all cookies - */ - public void removeAllCookie() { - final Runnable clearCache = new Runnable() { - public void run() { - synchronized(CookieManager.this) { - mCookieMap = new LinkedHashMap<String, ArrayList<Cookie>>( - MAX_DOMAIN_COUNT, 0.75f, true); - CookieSyncManager.getInstance().clearAllCookies(); - } - } - }; - new Thread(clearCache).start(); - } - - /** - * Return true if there are stored cookies. - */ - public synchronized boolean hasCookies() { - return CookieSyncManager.getInstance().hasCookies(); - } - - /** - * Remove all expired cookies - */ - public void removeExpiredCookie() { - final Runnable clearCache = new Runnable() { - public void run() { - synchronized(CookieManager.this) { - long now = System.currentTimeMillis(); - Collection<ArrayList<Cookie>> cookieList = mCookieMap.values(); - Iterator<ArrayList<Cookie>> listIter = cookieList.iterator(); - while (listIter.hasNext()) { - ArrayList<Cookie> list = listIter.next(); - Iterator<Cookie> iter = list.iterator(); - while (iter.hasNext()) { - Cookie cookie = iter.next(); - // expires == -1 means no expires defined. Otherwise - // negative means far future - if (cookie.expires > 0 && cookie.expires < now) { - iter.remove(); - } - } - } - CookieSyncManager.getInstance().clearExpiredCookies(now); - } - } - }; - new Thread(clearCache).start(); - } - - /** - * Package level api, called from CookieSyncManager - * - * Get a list of cookies which are updated since a given time. - * @param last The given time in millisec - * @return A list of cookies - */ - synchronized ArrayList<Cookie> getUpdatedCookiesSince(long last) { - ArrayList<Cookie> cookies = new ArrayList<Cookie>(); - Collection<ArrayList<Cookie>> cookieList = mCookieMap.values(); - Iterator<ArrayList<Cookie>> listIter = cookieList.iterator(); - while (listIter.hasNext()) { - ArrayList<Cookie> list = listIter.next(); - Iterator<Cookie> iter = list.iterator(); - while (iter.hasNext()) { - Cookie cookie = iter.next(); - if (cookie.lastUpdateTime > last) { - cookies.add(cookie); - } - } - } - return cookies; - } - - /** - * Package level api, called from CookieSyncManager - * - * Delete a Cookie in the RAM - * @param cookie Cookie to be deleted - */ - synchronized void deleteACookie(Cookie cookie) { - if (cookie.mode == Cookie.MODE_DELETED) { - String baseDomain = getBaseDomain(cookie.domain); - ArrayList<Cookie> cookieList = mCookieMap.get(baseDomain); - if (cookieList != null) { - cookieList.remove(cookie); - if (cookieList.isEmpty()) { - mCookieMap.remove(baseDomain); - } - } - } - } - - /** - * Package level api, called from CookieSyncManager - * - * Called after a cookie is synced to FLASH - * @param cookie Cookie to be synced - */ - synchronized void syncedACookie(Cookie cookie) { - cookie.mode = Cookie.MODE_NORMAL; - } - - /** - * Package level api, called from CookieSyncManager - * - * Delete the least recent used domains if the total cookie count in RAM - * exceeds the limit - * @return A list of cookies which are removed from RAM - */ - synchronized ArrayList<Cookie> deleteLRUDomain() { - int count = 0; - int byteCount = 0; - int mapSize = mCookieMap.size(); - - if (mapSize < MAX_RAM_DOMAIN_COUNT) { - Collection<ArrayList<Cookie>> cookieLists = mCookieMap.values(); - Iterator<ArrayList<Cookie>> listIter = cookieLists.iterator(); - while (listIter.hasNext() && count < MAX_RAM_COOKIES_COUNT) { - ArrayList<Cookie> list = listIter.next(); - if (Config.DEBUG) { - Iterator<Cookie> iter = list.iterator(); - while (iter.hasNext() && count < MAX_RAM_COOKIES_COUNT) { - Cookie cookie = iter.next(); - // 14 is 3 * sizeof(long) + sizeof(boolean) - // + sizeof(byte) - byteCount += cookie.domain.length() - + cookie.path.length() - + cookie.name.length() - + cookie.value.length() + 14; - count++; - } - } else { - count += list.size(); - } - } - } - - ArrayList<Cookie> retlist = new ArrayList<Cookie>(); - if (mapSize >= MAX_RAM_DOMAIN_COUNT || count >= MAX_RAM_COOKIES_COUNT) { - if (Config.DEBUG) { - Log.v(LOGTAG, count + " cookies used " + byteCount - + " bytes with " + mapSize + " domains"); - } - Object[] domains = mCookieMap.keySet().toArray(); - int toGo = mapSize / 10 + 1; - while (toGo-- > 0){ - String domain = domains[toGo].toString(); - if (Config.LOGV) { - Log.v(LOGTAG, "delete domain: " + domain - + " from RAM cache"); - } - retlist.addAll(mCookieMap.get(domain)); - mCookieMap.remove(domain); - } - } - return retlist; - } - - /** - * Extract the host and path out of a uri - * @param uri The given WebAddress - * @return The host and path in the format of String[], String[0] is host - * which has at least two periods, String[1] is path which always - * ended with "/" - */ - private String[] getHostAndPath(WebAddress uri) { - if (uri.mHost != null && uri.mPath != null) { - String[] ret = new String[2]; - ret[0] = uri.mHost; - ret[1] = uri.mPath; - - int index = ret[0].indexOf(PERIOD); - if (index == -1) { - if (uri.mScheme.equalsIgnoreCase("file")) { - // There is a potential bug where a local file path matches - // another file in the local web server directory. Still - // "localhost" is the best pseudo domain name. - ret[0] = "localhost"; - } else if (!ret[0].equals("localhost")) { - return null; - } - } else if (index == ret[0].lastIndexOf(PERIOD)) { - // cookie host must have at least two periods - ret[0] = PERIOD + ret[0]; - } - - if (ret[1].charAt(0) != PATH_DELIM) { - return null; - } - - /* - * 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/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); - } - return ret; - } else - return null; - } - - /** - * Get the base domain for a give host. E.g. mail.google.com will return - * google.com - * @param host The give host - * @return the base domain - */ - private String getBaseDomain(String host) { - int startIndex = 0; - int nextIndex = host.indexOf(PERIOD); - int lastIndex = host.lastIndexOf(PERIOD); - while (nextIndex < lastIndex) { - startIndex = nextIndex + 1; - nextIndex = host.indexOf(PERIOD, startIndex); - } - if (startIndex > 0) { - return host.substring(startIndex); - } else { - return host; - } - } - - /** - * parseCookie() parses the cookieString which is a comma-separated list of - * one or more cookies in the format of "NAME=VALUE; expires=DATE; - * path=PATH; domain=DOMAIN_NAME; secure httponly" to a list of Cookies. - * Here is a sample: IGDND=1, IGPC=ET=UB8TSNwtDmQ:AF=0; expires=Sun, - * 17-Jan-2038 19:14:07 GMT; path=/ig; domain=.google.com, =, - * PREF=ID=408909b1b304593d:TM=1156459854:LM=1156459854:S=V-vCAU6Sh-gobCfO; - * expires=Sun, 17-Jan-2038 19:14:07 GMT; path=/; domain=.google.com which - * contains 3 cookies IGDND, IGPC, PREF and an empty cookie - * @param host The default host - * @param path The default path - * @param cookieString The string coming from "Set-Cookie:" - * @return A list of Cookies - */ - private ArrayList<Cookie> parseCookie(String host, String path, - String cookieString) { - ArrayList<Cookie> ret = new ArrayList<Cookie>(); - - int index = 0; - int length = cookieString.length(); - while (true) { - Cookie cookie = null; - - // done - if (index < 0 || index >= length) { - break; - } - - // skip white space - if (cookieString.charAt(index) == WHITE_SPACE) { - index++; - continue; - } - - /* - * get NAME=VALUE; pair. detecting the end of a pair is tricky, it - * can be the end of a string, like "foo=bluh", it can be semicolon - * like "foo=bluh;path=/"; or it can be enclosed by \", like - * "foo=\"bluh bluh\";path=/" - * - * Note: in the case of "foo=bluh, bar=bluh;path=/", we interpret - * it as one cookie instead of two cookies. - */ - int equalIndex = cookieString.indexOf(EQUAL, index); - if (equalIndex == -1) { - // bad format, force return - break; - } - cookie = new Cookie(host, path); - cookie.name = cookieString.substring(index, equalIndex); - if (cookieString.charAt(equalIndex + 1) == QUOTATION) { - index = cookieString.indexOf(QUOTATION, equalIndex + 2); - if (index == -1) { - // bad format, force return - break; - } - } - int semicolonIndex = cookieString.indexOf(SEMICOLON, index); - if (semicolonIndex == -1) { - semicolonIndex = length; - } - if (semicolonIndex - equalIndex > MAX_COOKIE_LENGTH) { - // cookie is too big, trim it - cookie.value = cookieString.substring(equalIndex + 1, - equalIndex + MAX_COOKIE_LENGTH); - } else if (equalIndex + 1 == semicolonIndex - || semicolonIndex < equalIndex) { - // these are unusual case like foo=; and foo; path=/ - cookie.value = ""; - } else { - cookie.value = cookieString.substring(equalIndex + 1, - semicolonIndex); - } - // get attributes - index = semicolonIndex; - while (true) { - // done - if (index < 0 || index >= length) { - break; - } - - // skip white space and semicolon - if (cookieString.charAt(index) == WHITE_SPACE - || cookieString.charAt(index) == SEMICOLON) { - index++; - continue; - } - - // comma means next cookie - if (cookieString.charAt(index) == COMMA) { - index++; - break; - } - - // "secure" is a known attribute doesn't use "="; - // while sites like live.com uses "secure=" - if (length - index > SECURE_LENGTH - && cookieString.substring(index, index + SECURE_LENGTH). - equalsIgnoreCase(SECURE)) { - index += SECURE_LENGTH; - cookie.secure = true; - if (cookieString.charAt(index) == EQUAL) index++; - continue; - } - - // "httponly" is a known attribute doesn't use "="; - // while sites like live.com uses "httponly=" - if (length - index > HTTP_ONLY_LENGTH - && cookieString.substring(index, - index + HTTP_ONLY_LENGTH). - equalsIgnoreCase(HTTP_ONLY)) { - index += HTTP_ONLY_LENGTH; - if (cookieString.charAt(index) == EQUAL) index++; - // FIXME: currently only parse the attribute - continue; - } - equalIndex = cookieString.indexOf(EQUAL, index); - if (equalIndex > 0) { - String name = cookieString.substring(index, equalIndex) - .toLowerCase(); - if (name.equals(EXPIRES)) { - int comaIndex = cookieString.indexOf(COMMA, equalIndex); - - // skip ',' in (Wdy, DD-Mon-YYYY HH:MM:SS GMT) or - // (Weekday, DD-Mon-YY HH:MM:SS GMT) if it applies. - // "Wednesday" is the longest Weekday which has length 9 - if ((comaIndex != -1) && - (comaIndex - equalIndex <= 10)) { - index = comaIndex + 1; - } - } - semicolonIndex = cookieString.indexOf(SEMICOLON, index); - int commaIndex = cookieString.indexOf(COMMA, index); - if (semicolonIndex == -1 && commaIndex == -1) { - index = length; - } else if (semicolonIndex == -1) { - index = commaIndex; - } else if (commaIndex == -1) { - index = semicolonIndex; - } else { - index = Math.min(semicolonIndex, commaIndex); - } - String value = - cookieString.substring(equalIndex + 1, index); - - // Strip quotes if they exist - if (value.length() > 2 && value.charAt(0) == QUOTATION) { - int endQuote = value.indexOf(QUOTATION, 1); - if (endQuote > 0) { - value = value.substring(1, endQuote); - } - } - if (name.equals(EXPIRES)) { - try { - cookie.expires = HttpDateTime.parse(value); - } catch (IllegalArgumentException ex) { - Log.e(LOGTAG, - "illegal format for expires: " + value); - } - } else if (name.equals(MAX_AGE)) { - try { - cookie.expires = System.currentTimeMillis() + 1000 - * Long.parseLong(value); - } catch (NumberFormatException ex) { - Log.e(LOGTAG, - "illegal format for max-age: " + value); - } - } else if (name.equals(PATH)) { - // only allow non-empty path value - if (value.length() > 0) { - 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 - if (!value.equals(host)) { - // no cross-site cookie - cookie.domain = null; - } - continue; - } catch (NumberFormatException ex) { - // ignore the exception, value is a host name - } - value = value.toLowerCase(); - 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] - if ((len == lastPeriod + 3) - && (len >= 6 && len <= 8)) { - String s = value.substring(1, lastPeriod); - if (Arrays.binarySearch(BAD_COUNTRY_2LDS, s) >= 0) { - cookie.domain = null; - continue; - } - } - cookie.domain = value; - } else { - // no cross-site or more specific sub-domain cookie - cookie.domain = null; - } - } - } else { - // bad format, force return - index = length; - } - } - if (cookie != null && cookie.domain != null) { - ret.add(cookie); - } - } - return ret; - } -} diff --git a/core/java/android/webkit/CookieSyncManager.java b/core/java/android/webkit/CookieSyncManager.java deleted file mode 100644 index f2511d8..0000000 --- a/core/java/android/webkit/CookieSyncManager.java +++ /dev/null @@ -1,205 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.webkit; - -import android.content.Context; -import android.util.Config; -import android.util.Log; -import android.webkit.CookieManager.Cookie; - -import java.util.ArrayList; -import java.util.Iterator; - -/** - * The class CookieSyncManager is used to synchronize the browser cookies - * between RAM and FLASH. To get the best performance, browser cookie is saved - * in RAM. We use a separate thread to sync the cookies between RAM and FLASH on - * a timer base. - * <p> - * To use the CookieSyncManager, the host application has to call the following - * when the application starts. - * <p> - * CookieSyncManager.createInstance(context) - * <p> - * To set up for sync, the host application has to call - * <p> - * CookieSyncManager.getInstance().startSync() - * <p> - * in its Activity.onResume(), and call - * <p> - * CookieSyncManager.getInstance().stopSync() - * <p> - * in its Activity.onStop(). - * <p> - * To get instant sync instead of waiting for the timer to trigger, the host can - * call - * <p> - * CookieSyncManager.getInstance().sync() - */ -public final class CookieSyncManager extends WebSyncManager { - - private static CookieSyncManager sRef; - - // time when last update happened - private long mLastUpdate; - - private CookieSyncManager(Context context) { - super(context, "CookieSyncManager"); - } - - /** - * Singleton access to a {@link CookieSyncManager}. An - * IllegalStateException will be thrown if - * {@link CookieSyncManager#createInstance(Context)} is not called before. - * - * @return CookieSyncManager - */ - public static synchronized CookieSyncManager getInstance() { - if (sRef == null) { - throw new IllegalStateException( - "CookieSyncManager::createInstance() needs to be called " - + "before CookieSyncManager::getInstance()"); - } - return sRef; - } - - /** - * Create a singleton CookieSyncManager within a context - * @param context - * @return CookieSyncManager - */ - public static synchronized CookieSyncManager createInstance( - Context context) { - if (sRef == null) { - sRef = new CookieSyncManager(context); - } - return sRef; - } - - /** - * Package level api, called from CookieManager Get all the cookies which - * matches a given base domain. - * @param domain - * @return A list of Cookie - */ - ArrayList<Cookie> getCookiesForDomain(String domain) { - // null mDataBase implies that the host application doesn't support - // persistent cookie. No sync needed. - if (mDataBase == null) { - return new ArrayList<Cookie>(); - } - - return mDataBase.getCookiesForDomain(domain); - } - - /** - * Package level api, called from CookieManager Clear all cookies in the - * database - */ - void clearAllCookies() { - // null mDataBase implies that the host application doesn't support - // persistent cookie. - if (mDataBase == null) { - return; - } - - mDataBase.clearCookies(); - } - - /** - * Returns true if there are any saved cookies. - */ - boolean hasCookies() { - // null mDataBase implies that the host application doesn't support - // persistent cookie. - if (mDataBase == null) { - return false; - } - - return mDataBase.hasCookies(); - } - - /** - * Package level api, called from CookieManager Clear all session cookies in - * the database - */ - void clearSessionCookies() { - // null mDataBase implies that the host application doesn't support - // persistent cookie. - if (mDataBase == null) { - return; - } - - mDataBase.clearSessionCookies(); - } - - /** - * Package level api, called from CookieManager Clear all expired cookies in - * the database - */ - void clearExpiredCookies(long now) { - // null mDataBase implies that the host application doesn't support - // persistent cookie. - if (mDataBase == null) { - return; - } - - mDataBase.clearExpiredCookies(now); - } - - protected void syncFromRamToFlash() { - if (Config.LOGV) { - Log.v(LOGTAG, "CookieSyncManager::syncFromRamToFlash STARTS"); - } - - if (!CookieManager.getInstance().acceptCookie()) { - return; - } - - ArrayList<Cookie> cookieList = CookieManager.getInstance() - .getUpdatedCookiesSince(mLastUpdate); - mLastUpdate = System.currentTimeMillis(); - syncFromRamToFlash(cookieList); - - ArrayList<Cookie> lruList = - CookieManager.getInstance().deleteLRUDomain(); - syncFromRamToFlash(lruList); - - if (Config.LOGV) { - Log.v(LOGTAG, "CookieSyncManager::syncFromRamToFlash DONE"); - } - } - - private void syncFromRamToFlash(ArrayList<Cookie> list) { - Iterator<Cookie> iter = list.iterator(); - while (iter.hasNext()) { - Cookie cookie = iter.next(); - if (cookie.mode != Cookie.MODE_NORMAL) { - if (cookie.mode != Cookie.MODE_NEW) { - mDataBase.deleteCookies(cookie.domain, cookie.path, - cookie.name); - } - if (cookie.mode != Cookie.MODE_DELETED) { - mDataBase.addCookie(cookie); - CookieManager.getInstance().syncedACookie(cookie); - } else { - CookieManager.getInstance().deleteACookie(cookie); - } - } - } - } -} diff --git a/core/java/android/webkit/DataLoader.java b/core/java/android/webkit/DataLoader.java deleted file mode 100644 index dcdc949..0000000 --- a/core/java/android/webkit/DataLoader.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.webkit; - -import org.apache.http.protocol.HTTP; - -import android.net.http.Headers; - -import java.io.ByteArrayInputStream; - -/** - * This class is a concrete implementation of StreamLoader that uses the - * content supplied as a URL as the source for the stream. The mimetype - * optionally provided in the URL is extracted and inserted into the HTTP - * response headers. - */ -class DataLoader extends StreamLoader { - - private String mContentType; // Content mimetype, if supplied in URL - - /** - * Constructor uses the dataURL as the source for an InputStream - * @param dataUrl data: URL string optionally containing a mimetype - * @param loadListener LoadListener to pass the content to - */ - DataLoader(String dataUrl, LoadListener loadListener) { - super(loadListener); - - String url = dataUrl.substring("data:".length()); - String content; - int commaIndex = url.indexOf(','); - if (commaIndex != -1) { - mContentType = url.substring(0, commaIndex); - content = url.substring(commaIndex + 1); - } else { - content = url; - } - mDataStream = new ByteArrayInputStream(content.getBytes()); - mContentLength = content.length(); - } - - @Override - protected boolean setupStreamAndSendStatus() { - mHandler.status(1, 1, 0, "OK"); - return true; - } - - @Override - protected void buildHeaders(Headers headers) { - if (mContentType != null) { - headers.setContentType(mContentType); - } - } - - /** - * Construct a DataLoader and instruct it to start loading. - * - * @param url data: URL string optionally containing a mimetype - * @param loadListener LoadListener to pass the content to - */ - public static void requestUrl(String url, LoadListener loadListener) { - DataLoader loader = new DataLoader(url, loadListener); - loader.load(); - } - -} diff --git a/core/java/android/webkit/DateSorter.java b/core/java/android/webkit/DateSorter.java deleted file mode 100644 index 750403b..0000000 --- a/core/java/android/webkit/DateSorter.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (C) 2006 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.webkit; - -import android.content.Context; -import android.content.res.Resources; - -import java.util.Calendar; -import java.util.Date; - -/** - * Sorts dates into the following groups: - * Today - * Yesterday - * five days ago - * one month ago - * older than a month ago - */ - -public class DateSorter { - - private static final String LOGTAG = "webkit"; - - /** must be >= 3 */ - public static final int DAY_COUNT = 5; - - 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(); - - /** - * @param context Application context - */ - public DateSorter(Context context) { - Resources resources = context.getResources(); - - Calendar c = Calendar.getInstance(); - beginningOfDay(c); - - // Create the bins - mBins[0] = c.getTimeInMillis(); // Today - c.roll(Calendar.DAY_OF_YEAR, -1); - mBins[1] = c.getTimeInMillis(); // Yesterday - c.roll(Calendar.DAY_OF_YEAR, -(NUM_DAYS_AGO - 1)); - mBins[2] = c.getTimeInMillis(); // Five days ago - 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); - mBins[4] = c.getTimeInMillis(); // Over one month ago - - // build labels - mLabels[0] = context.getText(com.android.internal.R.string.today).toString(); - mLabels[1] = context.getText(com.android.internal.R.string.yesterday).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(); - } - - /** - * @param time time since the Epoch in milliseconds, such as that - * returned by Calendar.getTimeInMillis() - * @return an index from 0 to (DAY_COUNT - 1) that identifies which - * date bin this date belongs to - */ - public int getIndex(long time) { - // Lame linear search - for (int i = 0; i < DAY_COUNT; i++) { - if (time > mBins[i]) return i; - } - return DAY_COUNT - 1; - } - - /** - * @param index date bin index as returned by getIndex() - * @return string label suitable for display to user - */ - public String getLabel(int index) { - return mLabels[index]; - } - - - /** - * @param index date bin index as returned by getIndex() - * @return date boundary at given index - */ - public long getBoundary(int index) { - return mBins[index]; - } - - /** - * Calcuate 12:00am by zeroing out hour, minute, second, millisecond - */ - private Calendar beginningOfDay(Calendar c) { - c.set(Calendar.HOUR_OF_DAY, 0); - c.set(Calendar.MINUTE, 0); - c.set(Calendar.SECOND, 0); - c.set(Calendar.MILLISECOND, 0); - return c; - } -} diff --git a/core/java/android/webkit/DownloadListener.java b/core/java/android/webkit/DownloadListener.java deleted file mode 100644 index dfaa1b9..0000000 --- a/core/java/android/webkit/DownloadListener.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.webkit; - -public interface DownloadListener { - - /** - * Notify the host application that a file should be downloaded - * @param url The full url to the content that should be downloaded - * @param userAgent the user agent to be used for the download. - * @param contentDisposition Content-disposition http header, if - * present. - * @param mimetype The mimetype of the content reported by the server - * @param contentLength The file size reported by the server - */ - public void onDownloadStart(String url, String userAgent, - String contentDisposition, String mimetype, long contentLength); - -} diff --git a/core/java/android/webkit/FileLoader.java b/core/java/android/webkit/FileLoader.java deleted file mode 100644 index 54a4c1d..0000000 --- a/core/java/android/webkit/FileLoader.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.webkit; - -import com.android.internal.R; - -import android.content.Context; -import android.content.res.AssetManager; -import android.net.http.EventHandler; -import android.net.http.Headers; -import android.os.Environment; - -import java.io.File; -import java.io.FileInputStream; - -/** - * This class is a concrete implementation of StreamLoader that uses a - * file or asset as the source for the stream. - * - */ -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 - * source. - * - * @param url Full file url pointing to content to be loaded - * @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 allowFileAccess) { - super(loadListener); - mIsAsset = asset; - mContext = context; - mAllowFileAccess = allowFileAccess; - - // clean the Url - int index = url.indexOf('?'); - if (mIsAsset) { - mPath = index > 0 ? URLUtil.stripAnchor( - url.substring(URLUtil.ASSET_BASE.length(), index)) : - URLUtil.stripAnchor(url.substring( - URLUtil.ASSET_BASE.length())); - } else { - mPath = index > 0 ? URLUtil.stripAnchor( - url.substring(URLUtil.FILE_BASE.length(), index)) : - URLUtil.stripAnchor(url.substring( - URLUtil.FILE_BASE.length())); - } - } - - @Override - protected boolean setupStreamAndSendStatus() { - try { - if (mIsAsset) { - try { - mDataStream = mContext.getAssets().open(mPath); - } catch (java.io.FileNotFoundException ex) { - // try the rest files included in the package - mDataStream = mContext.getAssets().openNonAsset(mPath); - } - } else { - if (!mAllowFileAccess) { - mHandler.error(EventHandler.FILE_ERROR, - 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(R.string.httpErrorFileNotFound) + - " " + ex.getMessage()); - return false; - - } catch (java.io.IOException ex) { - mHandler.error(EventHandler.FILE_ERROR, - mContext.getString(R.string.httpErrorFileNotFound) + - " " + ex.getMessage()); - return false; - } - return true; - } - - @Override - protected void buildHeaders(Headers headers) { - // do nothing. - } - - - /** - * Construct a FileLoader and instruct it to start loading. - * - * @param url Full file url pointing to content to be loaded - * @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, 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 deleted file mode 100644 index 5e323eb..0000000 --- a/core/java/android/webkit/FrameLoader.java +++ /dev/null @@ -1,370 +0,0 @@ -/* - * Copyright (C) 2006 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.webkit; - -import android.net.http.EventHandler; -import android.net.http.RequestHandle; -import android.util.Config; -import android.util.Log; -import android.webkit.CacheManager.CacheResult; -import android.webkit.UrlInterceptRegistry; - -import java.util.HashMap; -import java.util.Map; - -class FrameLoader { - - 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; - - private static final String CONTENT_TYPE = "content-type"; - - // Contents of an about:blank page - private static final String mAboutBlank = - "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EB\">" + - "<html><head><title>about:blank</title></head><body></body></html>"; - - static final String HEADER_STR = "text/xml, text/html, " + - "application/xhtml+xml, image/png, text/plain, */*;q=0.8"; - - private static final String LOGTAG = "webkit"; - - FrameLoader(LoadListener listener, WebSettings settings, - String method, boolean highPriority) { - mListener = listener; - mHeaders = null; - mMethod = method; - mIsHighPriority = highPriority; - mCacheMode = WebSettings.LOAD_NORMAL; - mSettings = settings; - } - - public void setReferrer(String ref) { - // only set referrer for http or https - if (URLUtil.isNetworkUrl(ref)) mReferrer = ref; - } - - public void setPostData(byte[] postData) { - mPostData = postData; - } - - public void setContentTypeForPost(String postContentType) { - mContentType = postContentType; - } - - public void setCacheMode(int cacheMode) { - mCacheMode = cacheMode; - } - - public void setHeaders(HashMap headers) { - mHeaders = headers; - } - - public LoadListener getLoadListener() { - return mListener; - } - - /** - * Issues the load request. - * - * Return value does not indicate if the load was successful or not. It - * simply indicates that the load request is reasonable. - * - * @return true if the load is reasonable. - */ - public boolean executeLoad() { - String url = mListener.url(); - - // Attempt to decode the percent-encoded url. - try { - url = new String(URLUtil.decode(url.getBytes())); - } catch (IllegalArgumentException e) { - // Fail with a bad url error if the decode fails. - mListener.error(EventHandler.ERROR_BAD_URL, - mListener.getContext().getString( - com.android.internal.R.string.httpErrorBadUrl)); - return false; - } - - 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(); - } else if (handleLocalFile(url, mListener, mSettings)) { - return true; - } - if (Config.LOGV) { - Log.v(LOGTAG, "FrameLoader.executeLoad: url protocol not supported:" - + mListener.url()); - } - mListener.error(EventHandler.ERROR_UNSUPPORTED_SCHEME, - mListener.getContext().getText( - com.android.internal.R.string.httpErrorUnsupportedScheme).toString()); - return false; - - } - - /* package */ - static boolean handleLocalFile(String url, LoadListener loadListener, - WebSettings settings) { - if (URLUtil.isAssetUrl(url)) { - FileLoader.requestUrl(url, loadListener, loadListener.getContext(), - true, settings.getAllowFileAccess()); - return true; - } else if (URLUtil.isFileUrl(url)) { - FileLoader.requestUrl(url, loadListener, loadListener.getContext(), - false, settings.getAllowFileAccess()); - return true; - } else if (URLUtil.isContentUrl(url)) { - // Send the raw url to the ContentLoader because it will do a - // permission check and the url has to match.. - ContentLoader.requestUrl(loadListener.url(), loadListener, - loadListener.getContext()); - return true; - } else if (URLUtil.isDataUrl(url)) { - DataLoader.requestUrl(url, loadListener); - return true; - } else if (URLUtil.isAboutUrl(url)) { - loadListener.data(mAboutBlank.getBytes(), mAboutBlank.length()); - loadListener.endData(); - return true; - } - return false; - } - - private boolean handleHTTPLoad() { - if (mHeaders == null) { - mHeaders = new HashMap<String, String>(); - } - populateStaticHeaders(); - populateHeaders(); - - // response was handled by UrlIntercept, don't issue HTTP request - if (handleUrlIntercept()) return true; - - // response was handled by Cache, don't issue HTTP request - if (handleCache()) { - // push the request data down to the LoadListener - // as response from the cache could be a redirect - // and we may need to initiate a network request if the cache - // can't satisfy redirect URL - mListener.setRequestData(mMethod, mHeaders, mPostData, - mIsHighPriority); - return true; - } - - if (Config.LOGV) { - Log.v(LOGTAG, "FrameLoader: http " + mMethod + " load for: " - + mListener.url()); - } - - boolean ret = false; - int error = EventHandler.ERROR_UNSUPPORTED_SCHEME; - - try { - ret = mNetwork.requestURL(mMethod, mHeaders, - mPostData, mListener, mIsHighPriority); - } catch (android.net.ParseException ex) { - error = EventHandler.ERROR_BAD_URL; - } catch (java.lang.RuntimeException ex) { - /* probably an empty header set by javascript. We want - the same result as bad URL */ - error = EventHandler.ERROR_BAD_URL; - } - if (!ret) { - mListener.error(error, mListener.getContext().getText( - EventHandler.errorStringResources[Math.abs(error)]).toString()); - return false; - } - return true; - } - - /* - * This function is used by handleUrlInterecpt and handleCache to - * setup a load from the byte stream in a CacheResult. - */ - private void startCacheLoad(CacheResult result) { - if (Config.LOGV) { - Log.v(LOGTAG, "FrameLoader: loading from cache: " - + mListener.url()); - } - // Tell the Listener respond with the cache file - CacheLoader cacheLoader = - new CacheLoader(mListener, result); - mListener.setCacheLoader(cacheLoader); - cacheLoader.load(); - } - - /* - * This function is used by handleHTTPLoad to allow URL - * interception. This can be used to provide alternative load - * methods such as locally stored versions or for debugging. - * - * Returns true if the response was handled by UrlIntercept. - */ - 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( - mListener.url(), mHeaders); - if(result != null) { - // Intercepted. The data is stored in result.stream. Setup - // a load from the CacheResult. - startCacheLoad(result); - return true; - } - // Not intercepted. Carry on as normal. - return false; - } - - /* - * This function is used by the handleHTTPLoad to setup the cache headers - * correctly. - * Returns true if the response was handled from the cache - */ - private boolean handleCache() { - switch (mCacheMode) { - // This mode is normally used for a reload, it instructs the http - // loader to not use the cached content. - case WebSettings.LOAD_NO_CACHE: - break; - - - // This mode is used when the content should only be loaded from - // the cache. If it is not there, then fail the load. This is used - // to load POST content in a history navigation. - case WebSettings.LOAD_CACHE_ONLY: { - CacheResult result = CacheManager.getCacheFile(mListener.url(), - null); - if (result != null) { - startCacheLoad(result); - } else { - // This happens if WebCore was first told that the POST - // response was in the cache, then when we try to use it - // it has gone. - // Generate a file not found error - int err = EventHandler.FILE_NOT_FOUND_ERROR; - mListener.error(err, mListener.getContext().getText( - EventHandler.errorStringResources[Math.abs(err)]) - .toString()); - } - return true; - } - - // This mode is for when the user is doing a history navigation - // in the browser and should returned cached content regardless - // of it's state. If it is not in the cache, then go to the - // network. - case WebSettings.LOAD_CACHE_ELSE_NETWORK: { - if (Config.LOGV) { - Log.v(LOGTAG, "FrameLoader: checking cache: " - + mListener.url()); - } - // Get the cache file name for the current URL, passing null for - // the validation headers causes no validation to occur - CacheResult result = CacheManager.getCacheFile(mListener.url(), - null); - if (result != null) { - startCacheLoad(result); - return true; - } - break; - } - - // This is the default case, which is to check to see if the - // content in the cache can be used. If it can be used, then - // use it. If it needs revalidation then the relevant headers - // are added to the request. - default: - case WebSettings.LOAD_NORMAL: - return mListener.checkCache(mHeaders); - }// end of switch - - return false; - } - - /** - * Add the static headers that don't change with each request. - */ - private void populateStaticHeaders() { - // Accept header should already be there as they are built by WebCore, - // but in the case they are missing, add some. - String accept = mHeaders.get("Accept"); - if (accept == null || accept.length() == 0) { - mHeaders.put("Accept", HEADER_STR); - } - mHeaders.put("Accept-Charset", "utf-8, iso-8859-1, utf-16, *;q=0.7"); - - String acceptLanguage = mSettings.getAcceptLanguage(); - if (acceptLanguage.length() > 0) { - mHeaders.put("Accept-Language", acceptLanguage); - } - - mHeaders.put("User-Agent", mSettings.getUserAgentString()); - } - - /** - * Add the content related headers. These headers contain user private data - * and is not used when we are proxying an untrusted request. - */ - private void populateHeaders() { - - if (mReferrer != null) mHeaders.put("Referer", mReferrer); - if (mContentType != null) mHeaders.put(CONTENT_TYPE, mContentType); - - // if we have an active proxy and have proxy credentials, do pre-emptive - // authentication to avoid an extra round-trip: - if (mNetwork.isValidProxySet()) { - String username; - String password; - /* The proxy credentials can be set in the Network thread */ - synchronized (mNetwork) { - username = mNetwork.getProxyUsername(); - password = mNetwork.getProxyPassword(); - } - if (username != null && password != null) { - // we collect credentials ONLY if the proxy scheme is BASIC!!! - String proxyHeader = RequestHandle.authorizationHeader(true); - mHeaders.put(proxyHeader, - "Basic " + RequestHandle.computeBasicAuthResponse( - username, password)); - } - } - - // Set cookie header - String cookie = CookieManager.getInstance().getCookie( - mListener.getWebAddress()); - if (cookie != null && cookie.length() > 0) { - mHeaders.put("cookie", cookie); - } - } -} diff --git a/core/java/android/webkit/HttpAuthHandler.java b/core/java/android/webkit/HttpAuthHandler.java deleted file mode 100644 index 48b9eec..0000000 --- a/core/java/android/webkit/HttpAuthHandler.java +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Copyright (C) 2006 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.webkit; - -import android.os.Bundle; -import android.os.Handler; -import android.os.Message; - -import java.util.ListIterator; -import java.util.LinkedList; - -/** - * HTTP authentication handler: local handler that takes care - * of HTTP authentication requests. This class is passed as a - * parameter to BrowserCallback.displayHttpAuthDialog and is - * meant to receive the user's response. - */ -public class HttpAuthHandler extends Handler { - /* It is important that the handler is in Network, because - * we want to share it accross multiple loaders and windows - * (like our subwindow and the main window). - */ - - private static final String LOGTAG = "network"; - - /** - * Network. - */ - private Network mNetwork; - - /** - * Loader queue. - */ - private LinkedList<LoadListener> mLoaderQueue; - - - // Message id for handling the user response - private final int AUTH_PROCEED = 100; - private final int AUTH_CANCEL = 200; - - /** - * Creates a new HTTP authentication handler with an empty - * loader queue - * - * @param network The parent network object - */ - /* package */ HttpAuthHandler(Network network) { - mNetwork = network; - mLoaderQueue = new LinkedList<LoadListener>(); - } - - - @Override - public void handleMessage(Message msg) { - LoadListener loader = null; - synchronized (mLoaderQueue) { - loader = mLoaderQueue.poll(); - } - - switch (msg.what) { - case AUTH_PROCEED: - String username = msg.getData().getString("username"); - String password = msg.getData().getString("password"); - - loader.handleAuthResponse(username, password); - break; - - case AUTH_CANCEL: - - mNetwork.resetHandlersAndStopLoading(loader.getFrame()); - break; - } - - processNextLoader(); - } - - - /** - * Proceed with the authorization with the given credentials - * - * @param username The username to use for authentication - * @param password The password to use for authentication - */ - public void proceed(String username, String password) { - Message msg = obtainMessage(AUTH_PROCEED); - msg.getData().putString("username", username); - msg.getData().putString("password", password); - sendMessage(msg); - } - - /** - * Cancel the authorization request - */ - public void cancel() { - sendMessage(obtainMessage(AUTH_CANCEL)); - } - - /** - * @return True if we can use user credentials on record - * (ie, if we did not fail trying to use them last time) - */ - public boolean useHttpAuthUsernamePassword() { - LoadListener loader = null; - synchronized (mLoaderQueue) { - loader = mLoaderQueue.peek(); - } - if (loader != null) { - return !loader.authCredentialsInvalid(); - } - - return false; - } - - /** - * Resets the HTTP-authentication request handler, removes - * all loaders that share the same BrowserFrame - * - * @param frame The browser frame - */ - /* package */ void reset(BrowserFrame frame) { - synchronized (mLoaderQueue) { - ListIterator<LoadListener> i = mLoaderQueue.listIterator(0); - while (i.hasNext()) { - LoadListener loader = i.next(); - if (frame == loader.getFrame()) { - i.remove(); - } - } - } - } - - /** - * Enqueues the loader, if the loader is the only element - * in the queue, starts processing the loader - * - * @param loader The loader that resulted in this http - * authentication request - */ - /* package */ void handleAuthRequest(LoadListener loader) { - boolean processNext = false; - - synchronized (mLoaderQueue) { - mLoaderQueue.offer(loader); - processNext = - (mLoaderQueue.size() == 1); - } - - if (processNext) { - processNextLoader(); - } - } - - /** - * Process the next loader in the queue (helper method) - */ - private void processNextLoader() { - LoadListener loader = null; - synchronized (mLoaderQueue) { - loader = mLoaderQueue.peek(); - } - if (loader != null) { - CallbackProxy proxy = loader.getFrame().getCallbackProxy(); - - String hostname = loader.proxyAuthenticate() ? - mNetwork.getProxyHostname() : loader.host(); - - String realm = loader.realm(); - - proxy.onReceivedHttpAuthRequest(this, hostname, realm); - } - } -} diff --git a/core/java/android/webkit/HttpDateTime.java b/core/java/android/webkit/HttpDateTime.java deleted file mode 100644 index c6ec2d2..0000000 --- a/core/java/android/webkit/HttpDateTime.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.webkit; - -import android.text.format.Time; - -import java.util.Calendar; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - - -class HttpDateTime { - - /* - * Regular expression for parsing HTTP-date. - * - * Wdy, DD Mon YYYY HH:MM:SS GMT - * RFC 822, updated by RFC 1123 - * - * Weekday, DD-Mon-YY HH:MM:SS GMT - * RFC 850, obsoleted by RFC 1036 - * - * Wdy Mon DD HH:MM:SS YYYY - * ANSI C's asctime() format - * - * with following variations - * - * Wdy, DD-Mon-YYYY HH:MM:SS GMT - * Wdy, (SP)D Mon YYYY HH:MM:SS GMT - * Wdy,DD Mon YYYY HH:MM:SS GMT - * Wdy, DD-Mon-YY HH:MM:SS GMT - * Wdy, DD Mon YYYY HH:MM:SS -HHMM - * Wdy, DD Mon YYYY HH:MM:SS - * Wdy Mon (SP)D HH:MM:SS YYYY - * Wdy Mon DD HH:MM:SS YYYY GMT - */ - private static final String HTTP_DATE_RFC_REGEXP = - "([0-9]{1,2})[- ]([A-Za-z]{3,3})[- ]([0-9]{2,4})[ ]" - + "([0-9][0-9]:[0-9][0-9]:[0-9][0-9])"; - - private static final String HTTP_DATE_ANSIC_REGEXP = - "[ ]([A-Za-z]{3,3})[ ]+([0-9]{1,2})[ ]" - + "([0-9][0-9]:[0-9][0-9]:[0-9][0-9])[ ]([0-9]{2,4})"; - - /** - * The compiled version of the HTTP-date regular expressions. - */ - private static final Pattern HTTP_DATE_RFC_PATTERN = - Pattern.compile(HTTP_DATE_RFC_REGEXP); - private static final Pattern HTTP_DATE_ANSIC_PATTERN = - Pattern.compile(HTTP_DATE_ANSIC_REGEXP); - - private static class TimeOfDay { - int hour; - int minute; - int second; - } - - public static Long parse(String timeString) - throws IllegalArgumentException { - - int date = 1; - int month = Calendar.JANUARY; - int year = 1970; - TimeOfDay timeOfDay = new TimeOfDay(); - - Matcher rfcMatcher = HTTP_DATE_RFC_PATTERN.matcher(timeString); - if (rfcMatcher.find()) { - date = getDate(rfcMatcher.group(1)); - month = getMonth(rfcMatcher.group(2)); - year = getYear(rfcMatcher.group(3)); - timeOfDay = getTime(rfcMatcher.group(4)); - } else { - Matcher ansicMatcher = HTTP_DATE_ANSIC_PATTERN.matcher(timeString); - if (ansicMatcher.find()) { - month = getMonth(ansicMatcher.group(1)); - date = getDate(ansicMatcher.group(2)); - timeOfDay = getTime(ansicMatcher.group(3)); - year = getYear(ansicMatcher.group(4)); - } else { - throw new IllegalArgumentException(); - } - } - - // FIXME: Y2038 BUG! - if (year >= 2038) { - year = 2038; - month = Calendar.JANUARY; - date = 1; - } - - Time time = new Time(Time.TIMEZONE_UTC); - time.set(timeOfDay.second, timeOfDay.minute, timeOfDay.hour, date, - month, year); - return time.toMillis(false /* use isDst */); - } - - private static int getDate(String dateString) { - if (dateString.length() == 2) { - return (dateString.charAt(0) - '0') * 10 - + (dateString.charAt(1) - '0'); - } else { - return (dateString.charAt(0) - '0'); - } - } - - /* - * jan = 9 + 0 + 13 = 22 - * feb = 5 + 4 + 1 = 10 - * mar = 12 + 0 + 17 = 29 - * apr = 0 + 15 + 17 = 32 - * may = 12 + 0 + 24 = 36 - * jun = 9 + 20 + 13 = 42 - * jul = 9 + 20 + 11 = 40 - * aug = 0 + 20 + 6 = 26 - * sep = 18 + 4 + 15 = 37 - * oct = 14 + 2 + 19 = 35 - * nov = 13 + 14 + 21 = 48 - * dec = 3 + 4 + 2 = 9 - */ - private static int getMonth(String monthString) { - int hash = Character.toLowerCase(monthString.charAt(0)) + - Character.toLowerCase(monthString.charAt(1)) + - Character.toLowerCase(monthString.charAt(2)) - 3 * 'a'; - switch (hash) { - case 22: - return Calendar.JANUARY; - case 10: - return Calendar.FEBRUARY; - case 29: - return Calendar.MARCH; - case 32: - return Calendar.APRIL; - case 36: - return Calendar.MAY; - case 42: - return Calendar.JUNE; - case 40: - return Calendar.JULY; - case 26: - return Calendar.AUGUST; - case 37: - return Calendar.SEPTEMBER; - case 35: - return Calendar.OCTOBER; - case 48: - return Calendar.NOVEMBER; - case 9: - return Calendar.DECEMBER; - default: - throw new IllegalArgumentException(); - } - } - - private static int getYear(String yearString) { - if (yearString.length() == 2) { - int year = (yearString.charAt(0) - '0') * 10 - + (yearString.charAt(1) - '0'); - if (year >= 70) { - return year + 1900; - } else { - return year + 2000; - } - } else - return (yearString.charAt(0) - '0') * 1000 - + (yearString.charAt(1) - '0') * 100 - + (yearString.charAt(2) - '0') * 10 - + (yearString.charAt(3) - '0'); - } - - private static TimeOfDay getTime(String timeString) { - TimeOfDay time = new TimeOfDay(); - time.hour = (timeString.charAt(0) - '0') * 10 - + (timeString.charAt(1) - '0'); - time.minute = (timeString.charAt(3) - '0') * 10 - + (timeString.charAt(4) - '0'); - time.second = (timeString.charAt(6) - '0') * 10 - + (timeString.charAt(7) - '0'); - return time; - } -} diff --git a/core/java/android/webkit/JWebCoreJavaBridge.java b/core/java/android/webkit/JWebCoreJavaBridge.java deleted file mode 100644 index a0049ac..0000000 --- a/core/java/android/webkit/JWebCoreJavaBridge.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright (C) 2006 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.webkit; - -import android.os.Handler; -import android.os.Message; -import android.util.Config; -import android.util.Log; - -final class JWebCoreJavaBridge extends Handler { - // Identifier for the timer message. - private static final int TIMER_MESSAGE = 1; - // ID for servicing functionptr queue - private static final int FUNCPTR_MESSAGE = 2; - // Log system identifier. - private static final String LOGTAG = "webkit-timers"; - - // Native object pointer for interacting in native code. - private int mNativeBridge; - // Instant timer is used to implement a timer that needs to fire almost - // immediately. - private boolean mHasInstantTimer; - // Reference count the pause/resume of timers - private int mPauseTimerRefCount; - - /** - * Construct a new JWebCoreJavaBridge to interface with - * WebCore timers and cookies. - */ - public JWebCoreJavaBridge() { - nativeConstructor(); - } - - @Override - protected void finalize() { - nativeFinalize(); - } - - /** - * handleMessage - * @param msg The dispatched message. - * - * The only accepted message currently is TIMER_MESSAGE - */ - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case TIMER_MESSAGE: { - PerfChecker checker = new PerfChecker(); - // clear the flag so that sharedTimerFired() can set a new timer - mHasInstantTimer = false; - sharedTimerFired(); - checker.responseAlert("sharedTimer"); - break; - } - case FUNCPTR_MESSAGE: - nativeServiceFuncPtrQueue(); - break; - } - } - - // called from JNI side - private void signalServiceFuncPtrQueue() { - Message msg = obtainMessage(FUNCPTR_MESSAGE); - sendMessage(msg); - } - - private native void nativeServiceFuncPtrQueue(); - - /** - * Pause all timers. - */ - public void pause() { - if (--mPauseTimerRefCount == 0) { - setDeferringTimers(true); - } - } - - /** - * Resume all timers. - */ - public void resume() { - if (++mPauseTimerRefCount == 1) { - setDeferringTimers(false); - } - } - - /** - * Set WebCore cache size. - * @param bytes The cache size in bytes. - */ - public native void setCacheSize(int bytes); - - /** - * Store a cookie string associated with a url. - * @param url The url to be used as a key for the cookie. - * @param docUrl The policy base url used by WebCore. - * @param value The cookie string to be stored. - */ - private void setCookies(String url, String docUrl, String value) { - if (value.contains("\r") || value.contains("\n")) { - // for security reason, filter out '\r' and '\n' from the cookie - int size = value.length(); - StringBuilder buffer = new StringBuilder(size); - int i = 0; - while (i != -1 && i < size) { - int ir = value.indexOf('\r', i); - int in = value.indexOf('\n', i); - int newi = (ir == -1) ? in : (in == -1 ? ir : (ir < in ? ir - : in)); - if (newi > i) { - buffer.append(value.subSequence(i, newi)); - } else if (newi == -1) { - buffer.append(value.subSequence(i, size)); - break; - } - i = newi + 1; - } - value = buffer.toString(); - } - CookieManager.getInstance().setCookie(url, value); - } - - /** - * Retrieve the cookie string for the given url. - * @param url The resource's url. - * @return A String representing the cookies for the given resource url. - */ - private String cookies(String url) { - return CookieManager.getInstance().getCookie(url); - } - - /** - * Returns whether cookies are enabled or not. - */ - private boolean cookiesEnabled() { - return CookieManager.getInstance().acceptCookie(); - } - - /** - * setSharedTimer - * @param timemillis The relative time when the timer should fire - */ - private void setSharedTimer(long timemillis) { - if (Config.LOGV) Log.v(LOGTAG, "setSharedTimer " + timemillis); - - if (timemillis <= 0) { - // we don't accumulate the sharedTimer unless it is a delayed - // request. This way we won't flood the message queue with - // WebKit messages. This should improve the browser's - // responsiveness to key events. - if (mHasInstantTimer) { - return; - } else { - mHasInstantTimer = true; - Message msg = obtainMessage(TIMER_MESSAGE); - sendMessageDelayed(msg, timemillis); - } - } else { - Message msg = obtainMessage(TIMER_MESSAGE); - sendMessageDelayed(msg, timemillis); - } - } - - /** - * Stop the shared timer. - */ - private void stopSharedTimer() { - if (Config.LOGV) { - Log.v(LOGTAG, "stopSharedTimer removing all timers"); - } - removeMessages(TIMER_MESSAGE); - mHasInstantTimer = false; - } - - private native void nativeConstructor(); - 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/JsPromptResult.java b/core/java/android/webkit/JsPromptResult.java deleted file mode 100644 index 9fcd1bc..0000000 --- a/core/java/android/webkit/JsPromptResult.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.webkit; - - -/** - * Public class for handling javascript prompt requests. A - * JsDialogHandlerInterface implentation will receive a jsPrompt call with a - * JsPromptResult parameter. This parameter is used to return a result to - * WebView. The client can call cancel() to cancel the dialog or confirm() with - * the user's input to confirm the dialog. - */ -public class JsPromptResult extends JsResult { - // String result of the prompt - private String mStringResult; - - /** - * Handle a confirmation response from the user. - */ - public void confirm(String result) { - mStringResult = result; - confirm(); - } - - /*package*/ JsPromptResult(CallbackProxy proxy) { - super(proxy, /* unused */ false); - } - - /*package*/ String getStringResult() { - return mStringResult; - } - - @Override - /*package*/ void handleDefault() { - mStringResult = null; - super.handleDefault(); - } -} diff --git a/core/java/android/webkit/JsResult.java b/core/java/android/webkit/JsResult.java deleted file mode 100644 index 0c86e0a..0000000 --- a/core/java/android/webkit/JsResult.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.webkit; - - -public class JsResult { - // This prevents a user from interacting with the result before WebCore is - // ready to handle it. - private boolean mReady; - // Tells us if the user tried to confirm or cancel the result before WebCore - // is ready. - private boolean mTriedToNotifyBeforeReady; - // This is a basic result of a confirm or prompt dialog. - protected boolean mResult; - // This is the caller of the prompt and is the object that is waiting. - protected final CallbackProxy mProxy; - // This is the default value of the result. - private final boolean mDefaultValue; - - /** - * Handle the result if the user cancelled the dialog. - */ - public final void cancel() { - mResult = false; - wakeUp(); - } - - /** - * Handle a confirmation response from the user. - */ - public final void confirm() { - mResult = true; - wakeUp(); - } - - /*package*/ JsResult(CallbackProxy proxy, boolean defaultVal) { - mProxy = proxy; - mDefaultValue = defaultVal; - } - - /*package*/ final boolean getResult() { - return mResult; - } - - /*package*/ final void setReady() { - mReady = true; - if (mTriedToNotifyBeforeReady) { - wakeUp(); - } - } - - /*package*/ void handleDefault() { - setReady(); - mResult = mDefaultValue; - wakeUp(); - } - - /* Wake up the WebCore thread. */ - protected final void wakeUp() { - if (mReady) { - synchronized (mProxy) { - mProxy.notify(); - } - } else { - mTriedToNotifyBeforeReady = true; - } - } -} diff --git a/core/java/android/webkit/LoadListener.java b/core/java/android/webkit/LoadListener.java deleted file mode 100644 index dfae17d..0000000 --- a/core/java/android/webkit/LoadListener.java +++ /dev/null @@ -1,1499 +0,0 @@ -/* - * Copyright (C) 2006 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.webkit; - -import android.content.Context; -import android.net.WebAddress; -import android.net.ParseException; -import android.net.http.EventHandler; -import android.net.http.Headers; -import android.net.http.HttpAuthHeader; -import android.net.http.RequestHandle; -import android.net.http.SslCertificate; -import android.net.http.SslError; -import android.net.http.SslCertificate; - -import android.os.Handler; -import android.os.Message; -import android.util.Config; -import android.util.Log; -import android.webkit.CacheManager.CacheResult; - -import com.android.internal.R; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; -import java.util.Vector; -import java.util.regex.Pattern; -import java.util.regex.Matcher; - -import org.apache.commons.codec.binary.Base64; - -class LoadListener extends Handler implements EventHandler { - - private static final String LOGTAG = "webkit"; - - // Messages used internally to communicate state between the - // Network thread and the WebCore thread. - private static final int MSG_CONTENT_HEADERS = 100; - private static final int MSG_CONTENT_DATA = 110; - private static final int MSG_CONTENT_FINISHED = 120; - private static final int MSG_CONTENT_ERROR = 130; - private static final int MSG_LOCATION_CHANGED = 140; - private static final int MSG_LOCATION_CHANGED_REQUEST = 150; - private static final int MSG_STATUS = 160; - private static final int MSG_SSL_CERTIFICATE = 170; - private static final int MSG_SSL_ERROR = 180; - - // Standard HTTP status codes in a more representative format - private static final int HTTP_OK = 200; - private static final int HTTP_MOVED_PERMANENTLY = 301; - private static final int HTTP_FOUND = 302; - private static final int HTTP_SEE_OTHER = 303; - private static final int HTTP_NOT_MODIFIED = 304; - private static final int HTTP_TEMPORARY_REDIRECT = 307; - private static final int HTTP_AUTH = 401; - private static final int HTTP_NOT_FOUND = 404; - private static final int HTTP_PROXY_AUTH = 407; - - private static int sNativeLoaderCount; - - private final ByteArrayBuilder mDataBuilder = new ByteArrayBuilder(8192); - - private String mUrl; - private WebAddress mUri; - private boolean mPermanent; - private String mOriginalUrl; - private Context mContext; - private BrowserFrame mBrowserFrame; - private int mNativeLoader; - private String mMimeType; - private String mEncoding; - private String mTransferEncoding; - private int mStatusCode; - private String mStatusText; - public long mContentLength; // Content length of the incoming data - private boolean mCancelled; // The request has been cancelled. - private boolean mAuthFailed; // indicates that the prev. auth failed - private CacheLoader mCacheLoader; - private CacheManager.CacheResult mCacheResult; - private HttpAuthHeader mAuthHeader; - private int mErrorID = OK; - private String mErrorDescription; - private SslError mSslError; - private RequestHandle mRequestHandle; - - // Request data. It is only valid when we are doing a load from the - // cache. It is needed if the cache returns a redirect - private String mMethod; - private Map<String, String> mRequestHeaders; - private byte[] mPostData; - private boolean mIsHighPriority; - // Flag to indicate that this load is synchronous. - private boolean mSynchronous; - private Vector<Message> mMessageQueue; - - // Does this loader correspond to the main-frame top-level page? - private boolean mIsMainPageLoader; - - private Headers mHeaders; - - // ========================================================================= - // Public functions - // ========================================================================= - - public static LoadListener getLoadListener( - Context context, BrowserFrame frame, String url, - int nativeLoader, boolean synchronous, boolean isMainPageLoader) { - - sNativeLoaderCount += 1; - return new LoadListener( - context, frame, url, nativeLoader, synchronous, isMainPageLoader); - } - - public static int getNativeLoaderCount() { - return sNativeLoaderCount; - } - - LoadListener(Context context, BrowserFrame frame, String url, - int nativeLoader, boolean synchronous, boolean isMainPageLoader) { - if (Config.LOGV) { - Log.v(LOGTAG, "LoadListener constructor url=" + url); - } - mContext = context; - mBrowserFrame = frame; - setUrl(url); - mNativeLoader = nativeLoader; - mMimeType = ""; - mEncoding = ""; - mSynchronous = synchronous; - if (synchronous) { - mMessageQueue = new Vector<Message>(); - } - mIsMainPageLoader = isMainPageLoader; - } - - /** - * We keep a count of refs to the nativeLoader so we do not create - * so many LoadListeners that the GREFs blow up - */ - private void clearNativeLoader() { - sNativeLoaderCount -= 1; - mNativeLoader = 0; - } - - /* - * This message handler is to facilitate communication between the network - * thread and the browser thread. - */ - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_CONTENT_HEADERS: - /* - * This message is sent when the LoadListener has headers - * available. The headers are sent onto WebCore to see what we - * should do with them. - */ - handleHeaders((Headers) msg.obj); - break; - - case MSG_CONTENT_DATA: - /* - * This message is sent when the LoadListener has data available - * in it's data buffer. This data buffer could be filled from a - * file (this thread) or from http (Network thread). - */ - if (mNativeLoader != 0 && !ignoreCallbacks()) { - commitLoad(); - } - break; - - case MSG_CONTENT_FINISHED: - /* - * This message is sent when the LoadListener knows that the - * load is finished. This message is not sent in the case of an - * error. - * - */ - handleEndData(); - break; - - case MSG_CONTENT_ERROR: - /* - * This message is sent when a load error has occured. The - * LoadListener will clean itself up. - */ - handleError(msg.arg1, (String) msg.obj); - break; - - case MSG_LOCATION_CHANGED: - /* - * This message is sent from LoadListener.endData to inform the - * browser activity that the location of the top level page - * changed. - */ - doRedirect(); - break; - - case MSG_LOCATION_CHANGED_REQUEST: - /* - * This message is sent from endData on receipt of a 307 - * Temporary Redirect in response to a POST -- the user must - * confirm whether to continue loading. If the user says Yes, - * we simply call MSG_LOCATION_CHANGED. If the user says No, - * we call MSG_CONTENT_FINISHED. - */ - Message contMsg = obtainMessage(MSG_LOCATION_CHANGED); - Message stopMsg = obtainMessage(MSG_CONTENT_FINISHED); - mBrowserFrame.getCallbackProxy().onFormResubmission( - stopMsg, contMsg); - break; - - case MSG_STATUS: - /* - * This message is sent from the network thread when the http - * stack has received the status response from the server. - */ - HashMap status = (HashMap) msg.obj; - handleStatus(((Integer) status.get("major")).intValue(), - ((Integer) status.get("minor")).intValue(), - ((Integer) status.get("code")).intValue(), - (String) status.get("reason")); - break; - - case MSG_SSL_CERTIFICATE: - /* - * This message is sent when the network thread receives a ssl - * certificate. - */ - handleCertificate((SslCertificate) msg.obj); - break; - - case MSG_SSL_ERROR: - /* - * This message is sent when the network thread encounters a - * ssl error. - */ - handleSslError((SslError) msg.obj); - break; - } - } - - /** - * @return The loader's BrowserFrame. - */ - BrowserFrame getFrame() { - return mBrowserFrame; - } - - Context getContext() { - return mContext; - } - - /* package */ boolean isSynchronous() { - return mSynchronous; - } - - /** - * @return True iff the load has been cancelled - */ - public boolean cancelled() { - return mCancelled; - } - - /** - * Parse the headers sent from the server. - * @param headers gives up the HeaderGroup - * IMPORTANT: as this is called from network thread, can't call native - * directly - */ - public void headers(Headers headers) { - if (Config.LOGV) Log.v(LOGTAG, "LoadListener.headers"); - sendMessageInternal(obtainMessage(MSG_CONTENT_HEADERS, headers)); - } - - // Does the header parsing work on the WebCore thread. - private void handleHeaders(Headers headers) { - if (mCancelled) return; - mHeaders = headers; - mMimeType = ""; - mEncoding = ""; - - ArrayList<String> cookies = headers.getSetCookie(); - for (int i = 0; i < cookies.size(); ++i) { - CookieManager.getInstance().setCookie(mUri, cookies.get(i)); - } - - long contentLength = headers.getContentLength(); - if (contentLength != Headers.NO_CONTENT_LENGTH) { - mContentLength = contentLength; - } else { - mContentLength = 0; - } - - String contentType = headers.getContentType(); - if (contentType != null) { - parseContentTypeHeader(contentType); - - // If we have one of "generic" MIME types, try to deduce - // the right MIME type from the file extension (if any): - if (mMimeType.equalsIgnoreCase("text/plain") || - mMimeType.equalsIgnoreCase("application/octet-stream")) { - - String newMimeType = guessMimeTypeFromExtension(); - if (newMimeType != null) { - mMimeType = newMimeType; - } - } 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. - // 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"; - } - } - } else { - /* Often when servers respond with 304 Not Modified or a - Redirect, then they don't specify a MIMEType. When this - occurs, the function below is called. In the case of - 304 Not Modified, the cached headers are used rather - than the headers that are returned from the server. */ - guessMimeType(); - } - - // is it an authentication request? - boolean mustAuthenticate = (mStatusCode == HTTP_AUTH || - mStatusCode == HTTP_PROXY_AUTH); - // is it a proxy authentication request? - boolean isProxyAuthRequest = (mStatusCode == HTTP_PROXY_AUTH); - // is this authentication request due to a failed attempt to - // authenticate ealier? - mAuthFailed = false; - - // if we tried to authenticate ourselves last time - if (mAuthHeader != null) { - // we failed, if we must to authenticate again now and - // we have a proxy-ness match - mAuthFailed = (mustAuthenticate && - isProxyAuthRequest == mAuthHeader.isProxy()); - - // if we did NOT fail and last authentication request was a - // proxy-authentication request - if (!mAuthFailed && mAuthHeader.isProxy()) { - Network network = Network.getInstance(mContext); - // if we have a valid proxy set - if (network.isValidProxySet()) { - /* The proxy credentials can be read in the WebCore thread - */ - synchronized (network) { - // save authentication credentials for pre-emptive proxy - // authentication - network.setProxyUsername(mAuthHeader.getUsername()); - network.setProxyPassword(mAuthHeader.getPassword()); - } - } - } - } - // it is only here that we can reset the last mAuthHeader object - // (if existed) and start a new one!!! - mAuthHeader = null; - if (mustAuthenticate) { - if (mStatusCode == HTTP_AUTH) { - mAuthHeader = parseAuthHeader( - headers.getWwwAuthenticate()); - } else { - mAuthHeader = parseAuthHeader( - headers.getProxyAuthenticate()); - // if successfully parsed the header - if (mAuthHeader != null) { - // mark the auth-header object as a proxy - mAuthHeader.setProxy(); - } - } - } - - // Only create a cache file if the server has responded positively. - if ((mStatusCode == HTTP_OK || - mStatusCode == HTTP_FOUND || - mStatusCode == HTTP_MOVED_PERMANENTLY || - mStatusCode == HTTP_TEMPORARY_REDIRECT) && - mNativeLoader != 0) { - // Content arriving from a StreamLoader (eg File, Cache or Data) - // will not be cached as they have the header: - // cache-control: no-store - mCacheResult = CacheManager.createCacheFile(mUrl, mStatusCode, - headers, mMimeType, false); - if (mCacheResult != null) { - mCacheResult.encoding = mEncoding; - } - } - commitHeadersCheckRedirect(); - } - - /** - * @return True iff this loader is in the proxy-authenticate state. - */ - boolean proxyAuthenticate() { - if (mAuthHeader != null) { - return mAuthHeader.isProxy(); - } - - return false; - } - - /** - * Report the status of the response. - * TODO: Comments about each parameter. - * IMPORTANT: as this is called from network thread, can't call native - * directly - */ - public void status(int majorVersion, int minorVersion, - int code, /* Status-Code value */ String reasonPhrase) { - if (Config.LOGV) { - Log.v(LOGTAG, "LoadListener: from: " + mUrl - + " major: " + majorVersion - + " minor: " + minorVersion - + " code: " + code - + " reason: " + reasonPhrase); - } - HashMap status = new HashMap(); - status.put("major", majorVersion); - status.put("minor", minorVersion); - status.put("code", code); - status.put("reason", reasonPhrase); - sendMessageInternal(obtainMessage(MSG_STATUS, status)); - } - - // Handle the status callback on the WebCore thread. - private void handleStatus(int major, int minor, int code, String reason) { - if (mCancelled) return; - - mStatusCode = code; - mStatusText = reason; - mPermanent = false; - } - - /** - * Implementation of certificate handler for EventHandler. - * Called every time a resource is loaded via a secure - * connection. In this context, can be called multiple - * times if we have redirects - * @param certificate The SSL certifcate - * IMPORTANT: as this is called from network thread, can't call native - * directly - */ - public void certificate(SslCertificate certificate) { - sendMessageInternal(obtainMessage(MSG_SSL_CERTIFICATE, certificate)); - } - - // Handle the certificate on the WebCore thread. - private void handleCertificate(SslCertificate certificate) { - // if this is the top-most main-frame page loader - if (mIsMainPageLoader) { - // update the browser frame (ie, the main frame) - mBrowserFrame.certificate(certificate); - } - } - - /** - * Implementation of error handler for EventHandler. - * Subclasses should call this method to have error fields set. - * @param id The error id described by EventHandler. - * @param description A string description of the error. - * IMPORTANT: as this is called from network thread, can't call native - * directly - */ - public void error(int id, String description) { - if (Config.LOGV) { - Log.v(LOGTAG, "LoadListener.error url:" + - url() + " id:" + id + " description:" + description); - } - sendMessageInternal(obtainMessage(MSG_CONTENT_ERROR, id, 0, description)); - } - - // Handle the error on the WebCore thread. - private void handleError(int id, String description) { - mErrorID = id; - mErrorDescription = description; - detachRequestHandle(); - notifyError(); - tearDown(); - } - - /** - * Add data to the internal collection of data. This function is used by - * the data: scheme, about: scheme and http/https schemes. - * @param data A byte array containing the content. - * @param length The length of data. - * IMPORTANT: as this is called from network thread, can't call native - * directly - * XXX: Unlike the other network thread methods, this method can do the - * work of decoding the data and appending it to the data builder because - * mDataBuilder is a thread-safe structure. - */ - public void data(byte[] data, int length) { - if (Config.LOGV) { - Log.v(LOGTAG, "LoadListener.data(): url: " + url()); - } - - // Decode base64 data - // Note: It's fine that we only decode base64 here and not in the other - // data call because the only caller of the stream version is not - // base64 encoded. - if ("base64".equalsIgnoreCase(mTransferEncoding)) { - if (length < data.length) { - byte[] trimmedData = new byte[length]; - System.arraycopy(data, 0, trimmedData, 0, length); - data = trimmedData; - } - data = Base64.decodeBase64(data); - length = data.length; - } - // Synchronize on mData because commitLoad may write mData to WebCore - // and we don't want to replace mData or mDataLength at the same time - // as a write. - boolean sendMessage = false; - synchronized (mDataBuilder) { - sendMessage = mDataBuilder.isEmpty(); - mDataBuilder.append(data, 0, length); - } - if (sendMessage) { - // Send a message whenever data comes in after a write to WebCore - sendMessageInternal(obtainMessage(MSG_CONTENT_DATA)); - } - } - - /** - * Event handler's endData call. Send a message to the handler notifying - * them that the data has finished. - * IMPORTANT: as this is called from network thread, can't call native - * directly - */ - public void endData() { - if (Config.LOGV) { - Log.v(LOGTAG, "LoadListener.endData(): url: " + url()); - } - sendMessageInternal(obtainMessage(MSG_CONTENT_FINISHED)); - } - - // Handle the end of data. - private void handleEndData() { - if (mCancelled) return; - - switch (mStatusCode) { - case HTTP_MOVED_PERMANENTLY: - // 301 - permanent redirect - mPermanent = true; - case HTTP_FOUND: - case HTTP_SEE_OTHER: - case HTTP_TEMPORARY_REDIRECT: - // 301, 302, 303, and 307 - redirect - if (mStatusCode == HTTP_TEMPORARY_REDIRECT) { - if (mRequestHandle != null && - mRequestHandle.getMethod().equals("POST")) { - sendMessageInternal(obtainMessage( - MSG_LOCATION_CHANGED_REQUEST)); - } else if (mMethod != null && mMethod.equals("POST")) { - sendMessageInternal(obtainMessage( - MSG_LOCATION_CHANGED_REQUEST)); - } else { - sendMessageInternal(obtainMessage(MSG_LOCATION_CHANGED)); - } - } else { - sendMessageInternal(obtainMessage(MSG_LOCATION_CHANGED)); - } - 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); - return; - } - 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 - // before calling it. - if (mCacheLoader != null) { - detachRequestHandle(); - mCacheLoader.load(); - if (Config.LOGV) { - Log.v(LOGTAG, "LoadListener cache load url=" + url()); - } - return; - } - break; // use default - - case HTTP_NOT_FOUND: - // Not an error, the server can send back content. - default: - break; - } - detachRequestHandle(); - tearDown(); - } - - /* This method is called from CacheLoader when the initial request is - * serviced by the Cache. */ - /* package */ void setCacheLoader(CacheLoader c) { - mCacheLoader = c; - } - - /** - * Check the cache for the current URL, and load it if it is valid. - * - * @param headers for the request - * @return true if cached response is used. - */ - boolean checkCache(Map<String, String> headers) { - // Get the cache file name for the current URL - CacheResult result = CacheManager.getCacheFile(url(), - headers); - - // Go ahead and set the cache loader to null in case the result is - // null. - mCacheLoader = null; - - if (result != null) { - // The contents of the cache may need to be revalidated so just - // remember the cache loader in the case that the server responds - // positively to the cached content. This is also used to detect if - // a redirect came from the cache. - mCacheLoader = new CacheLoader(this, result); - - // If I got a cachedUrl and the revalidation header was not - // added, then the cached content valid, we should use it. - if (!headers.containsKey( - CacheManager.HEADER_KEY_IFNONEMATCH) && - !headers.containsKey( - CacheManager.HEADER_KEY_IFMODIFIEDSINCE)) { - if (Config.LOGV) { - Log.v(LOGTAG, "FrameLoader: HTTP URL in cache " + - "and usable: " + url()); - } - // Load the cached file - mCacheLoader.load(); - return true; - } - } - return false; - } - - /** - * SSL certificate error callback. Handles SSL error(s) on the way up - * to the user. - * IMPORTANT: as this is called from network thread, can't call native - * directly - */ - public void handleSslErrorRequest(SslError error) { - if (Config.LOGV) { - Log.v(LOGTAG, - "LoadListener.handleSslErrorRequest(): url:" + url() + - " primary error: " + error.getPrimaryError() + - " certificate: " + error.getCertificate()); - } - sendMessageInternal(obtainMessage(MSG_SSL_ERROR, error)); - } - - // Handle the ssl error on the WebCore thread. - private void handleSslError(SslError error) { - if (!mCancelled) { - mSslError = error; - Network.getInstance(mContext).handleSslErrorRequest(this); - } - } - - /** - * @return HTTP authentication realm or null if none. - */ - String realm() { - if (mAuthHeader == null) { - return null; - } else { - return mAuthHeader.getRealm(); - } - } - - /** - * Returns true iff an HTTP authentication problem has - * occured (credentials invalid). - */ - boolean authCredentialsInvalid() { - // if it is digest and the nonce is stale, we just - // resubmit with a new nonce - return (mAuthFailed && - !(mAuthHeader.isDigest() && mAuthHeader.getStale())); - } - - /** - * @return The last SSL error or null if there is none - */ - SslError sslError() { - return mSslError; - } - - /** - * Handles SSL error(s) on the way down from the user - * (the user has already provided their feedback). - */ - void handleSslErrorResponse(boolean proceed) { - if (mRequestHandle != null) { - mRequestHandle.handleSslErrorResponse(proceed); - } - } - - /** - * Uses user-supplied credentials to restar a request. - */ - void handleAuthResponse(String username, String password) { - if (Config.LOGV) { - Log.v(LOGTAG, "LoadListener.handleAuthResponse: url: " + mUrl - + " username: " + username - + " password: " + password); - } - - // create and queue an authentication-response - if (username != null && password != null) { - if (mAuthHeader != null && mRequestHandle != null) { - mAuthHeader.setUsername(username); - mAuthHeader.setPassword(password); - - int scheme = mAuthHeader.getScheme(); - if (scheme == HttpAuthHeader.BASIC) { - // create a basic response - boolean isProxy = mAuthHeader.isProxy(); - - mRequestHandle.setupBasicAuthResponse(isProxy, - username, password); - } else { - if (scheme == HttpAuthHeader.DIGEST) { - // create a digest response - boolean isProxy = mAuthHeader.isProxy(); - - String realm = mAuthHeader.getRealm(); - String nonce = mAuthHeader.getNonce(); - String qop = mAuthHeader.getQop(); - String algorithm = mAuthHeader.getAlgorithm(); - String opaque = mAuthHeader.getOpaque(); - - mRequestHandle.setupDigestAuthResponse - (isProxy, username, password, realm, - nonce, qop, algorithm, opaque); - } - } - } - } - } - - /** - * This is called when a request can be satisfied by the cache, however, - * the cache result could be a redirect. In this case we need to issue - * the network request. - * @param method - * @param headers - * @param postData - * @param isHighPriority - */ - void setRequestData(String method, Map<String, String> headers, - byte[] postData, boolean isHighPriority) { - mMethod = method; - mRequestHeaders = headers; - mPostData = postData; - mIsHighPriority = isHighPriority; - } - - /** - * @return The current URL associated with this load. - */ - String url() { - return mUrl; - } - - /** - * @return The current WebAddress associated with this load. - */ - WebAddress getWebAddress() { - return mUri; - } - - /** - * @return URL hostname (current URL). - */ - String host() { - if (mUri != null) { - return mUri.mHost; - } - - return null; - } - - /** - * @return The original URL associated with this load. - */ - String originalUrl() { - if (mOriginalUrl != null) { - return mOriginalUrl; - } else { - return mUrl; - } - } - - void attachRequestHandle(RequestHandle requestHandle) { - if (Config.LOGV) { - Log.v(LOGTAG, "LoadListener.attachRequestHandle(): " + - "requestHandle: " + requestHandle); - } - mRequestHandle = requestHandle; - } - - void detachRequestHandle() { - if (Config.LOGV) { - Log.v(LOGTAG, "LoadListener.detachRequestHandle(): " + - "requestHandle: " + mRequestHandle); - } - mRequestHandle = null; - } - - /* - * This function is called from native WebCore code to - * notify this LoadListener that the content it is currently - * downloading should be saved to a file and not sent to - * WebCore. - */ - void downloadFile() { - // Setting the Cache Result to null ensures that this - // content is not added to the cache - mCacheResult = null; - - // Inform the client that they should download a file - mBrowserFrame.getCallbackProxy().onDownloadStart(url(), - mBrowserFrame.getUserAgentString(), - mHeaders.getContentDisposition(), - mMimeType, mContentLength); - - // Cancel the download. We need to stop the http load. - // The native loader object will get cleared by the call to - // cancel() but will also be cleared on the WebCore side - // when this function returns. - cancel(); - } - - /* - * This function is called from native WebCore code to - * find out if the given URL is in the cache, and if it can - * be used. This is just for forward/back navigation to a POST - * URL. - */ - static boolean willLoadFromCache(String url) { - boolean inCache = CacheManager.getCacheFile(url, null) != null; - if (Config.LOGV) { - Log.v(LOGTAG, "willLoadFromCache: " + url + " in cache: " + - inCache); - } - return inCache; - } - - /* - * Reset the cancel flag. This is used when we are resuming a stopped - * download. To suspend a download, we cancel it. It can also be cancelled - * when it has run out of disk space. In this situation, the download - * can be resumed. - */ - void resetCancel() { - mCancelled = false; - } - - String mimeType() { - return mMimeType; - } - - /* - * Return the size of the content being downloaded. This represents the - * full content size, even under the situation where the download has been - * resumed after interruption. - * - * @ return full content size - */ - long contentLength() { - return mContentLength; - } - - // Commit the headers if the status code is not a redirect. - private void commitHeadersCheckRedirect() { - if (mCancelled) return; - - // do not call webcore if it is redirect. According to the code in - // InspectorController::willSendRequest(), the response is only updated - // when it is not redirect. - if ((mStatusCode >= 301 && mStatusCode <= 303) || mStatusCode == 307) { - return; - } - - commitHeaders(); - } - - // This commits the headers without checking the response status code. - private void commitHeaders() { - // Commit the headers to WebCore - int nativeResponse = createNativeResponse(); - // The native code deletes the native response object. - nativeReceivedResponse(nativeResponse); - } - - /** - * Create a WebCore response object so that it can be used by - * nativeReceivedResponse or nativeRedirectedToUrl - * @return native response pointer - */ - private int createNativeResponse() { - // The reason we change HTTP_NOT_MODIFIED to HTTP_OK is because we know - // that WebCore never sends the if-modified-since header. Our - // CacheManager does it for us. If the server responds with a 304, then - // we treat it like it was a 200 code and proceed with loading the file - // from the cache. - int statusCode = mStatusCode == HTTP_NOT_MODIFIED - ? HTTP_OK : mStatusCode; - // pass content-type content-length and content-encoding - final int nativeResponse = nativeCreateResponse( - mUrl, statusCode, mStatusText, - mMimeType, mContentLength, mEncoding, - mCacheResult == null ? 0 : mCacheResult.expires / 1000); - if (mHeaders != null) { - mHeaders.getHeaders(new Headers.HeaderCallback() { - public void header(String name, String value) { - nativeSetResponseHeader(nativeResponse, name, value); - } - }); - } - return nativeResponse; - } - - /** - * Commit the load. It should be ok to call repeatedly but only before - * tearDown is called. - */ - private void commitLoad() { - if (mCancelled) return; - - // Give the data to WebKit now - PerfChecker checker = new PerfChecker(); - ByteArrayBuilder.Chunk c; - while (true) { - c = mDataBuilder.getFirstChunk(); - if (c == null) break; - - if (c.mLength != 0) { - if (mCacheResult != null) { - try { - mCacheResult.outStream.write(c.mArray, 0, c.mLength); - } catch (IOException e) { - mCacheResult = null; - } - } - nativeAddData(c.mArray, c.mLength); - } - mDataBuilder.releaseChunk(c); - checker.responseAlert("res nativeAddData"); - } - } - - /** - * Tear down the load. Subclasses should clean up any mess because of - * cancellation or errors during the load. - */ - void tearDown() { - if (mCacheResult != null) { - if (getErrorID() == OK) { - CacheManager.saveCacheFile(mUrl, mCacheResult); - } - - // we need to reset mCacheResult to be null - // resource loader's tearDown will call into WebCore's - // nativeFinish, which in turn calls loader.cancel(). - // If we don't reset mCacheFile, the file will be deleted. - mCacheResult = null; - } - if (mNativeLoader != 0) { - PerfChecker checker = new PerfChecker(); - nativeFinished(); - checker.responseAlert("res nativeFinished"); - clearNativeLoader(); - } - } - - /** - * Helper for getting the error ID. - * @return errorID. - */ - private int getErrorID() { - return mErrorID; - } - - /** - * Return the error description. - * @return errorDescription. - */ - private String getErrorDescription() { - return mErrorDescription; - } - - /** - * Notify the loader we encountered an error. - */ - void notifyError() { - if (mNativeLoader != 0) { - String description = getErrorDescription(); - if (description == null) description = ""; - nativeError(getErrorID(), description, url()); - clearNativeLoader(); - } - } - - /** - * Cancel a request. - * FIXME: This will only work if the request has yet to be handled. This - * is in no way guarenteed if requests are served in a separate thread. - * It also causes major problems if cancel is called during an - * EventHandler's method call. - */ - public void cancel() { - if (Config.LOGV) { - if (mRequestHandle == null) { - Log.v(LOGTAG, "LoadListener.cancel(): no requestHandle"); - } else { - Log.v(LOGTAG, "LoadListener.cancel()"); - } - } - if (mRequestHandle != null) { - mRequestHandle.cancel(); - mRequestHandle = null; - } - - mCacheResult = null; - mCancelled = true; - - clearNativeLoader(); - } - - // This count is transferred from RequestHandle to LoadListener when - // loading from the cache so that we can detect redirect loops that switch - // between the network and the cache. - private int mCacheRedirectCount; - - /* - * Perform the actual redirection. This involves setting up the new URL, - * informing WebCore and then telling the Network to start loading again. - */ - private void doRedirect() { - // as cancel() can cancel the load before doRedirect() is - // called through handleMessage, needs to check to see if we - // are canceled before proceed - if (mCancelled) { - return; - } - - // Do the same check for a redirect loop that - // RequestHandle.setupRedirect does. - if (mCacheRedirectCount >= RequestHandle.MAX_REDIRECT_COUNT) { - handleError(EventHandler.ERROR_REDIRECT_LOOP, mContext.getString( - R.string.httpErrorRedirectLoop)); - return; - } - - String redirectTo = mHeaders.getLocation(); - if (redirectTo != null) { - int nativeResponse = createNativeResponse(); - redirectTo = - nativeRedirectedToUrl(mUrl, redirectTo, nativeResponse); - // nativeRedirectedToUrl() may call cancel(), e.g. when redirect - // from a https site to a http site, check mCancelled again - if (mCancelled) { - return; - } - if (redirectTo == null) { - Log.d(LOGTAG, "Redirection failed for " - + mHeaders.getLocation()); - cancel(); - return; - } else if (!URLUtil.isNetworkUrl(redirectTo)) { - final String text = mContext - .getString(R.string.open_permission_deny) - + "\n" + redirectTo; - nativeAddData(text.getBytes(), text.length()); - nativeFinished(); - clearNativeLoader(); - return; - } - - if (mOriginalUrl == null) { - mOriginalUrl = mUrl; - } - - // Cache the redirect response - if (mCacheResult != null) { - if (getErrorID() == OK) { - CacheManager.saveCacheFile(mUrl, mCacheResult); - } - mCacheResult = null; - } - - setUrl(redirectTo); - - // Redirect may be in the cache - if (mRequestHeaders == null) { - mRequestHeaders = new HashMap<String, String>(); - } - boolean fromCache = false; - if (mCacheLoader != null) { - // This is a redirect from the cache loader. Increment the - // redirect count to avoid redirect loops. - mCacheRedirectCount++; - fromCache = true; - } - if (!checkCache(mRequestHeaders)) { - // mRequestHandle can be null when the request was satisfied - // by the cache, and the cache returned a redirect - if (mRequestHandle != null) { - mRequestHandle.setupRedirect(redirectTo, mStatusCode, - mRequestHeaders); - } else { - // If the original request came from the cache, there is no - // RequestHandle, we have to create a new one through - // Network.requestURL. - Network network = Network.getInstance(getContext()); - if (!network.requestURL(mMethod, mRequestHeaders, - mPostData, this, mIsHighPriority)) { - // Signal a bad url error if we could not load the - // redirection. - handleError(EventHandler.ERROR_BAD_URL, - mContext.getString(R.string.httpErrorBadUrl)); - return; - } - } - if (fromCache) { - // If we are coming from a cache load, we need to transfer - // the redirect count to the new (or old) RequestHandle to - // keep the redirect count in sync. - mRequestHandle.setRedirectCount(mCacheRedirectCount); - } - } else if (!fromCache) { - // Switching from network to cache means we need to grab the - // redirect count from the RequestHandle to keep the count in - // sync. Add 1 to account for the current redirect. - mCacheRedirectCount = mRequestHandle.getRedirectCount() + 1; - } - // Clear the buffered data since the redirect is valid. - mDataBuilder.clear(); - } else { - commitHeaders(); - commitLoad(); - tearDown(); - } - - if (Config.LOGV) { - Log.v(LOGTAG, "LoadListener.onRedirect(): redirect to: " + - redirectTo); - } - } - - /** - * Parses the content-type header. - */ - private static final Pattern CONTENT_TYPE_PATTERN = - Pattern.compile("^([a-zA-Z\\*]+/[\\w\\+\\*-]+[\\.[\\w\\+-]+]*)$"); - - private void parseContentTypeHeader(String contentType) { - if (Config.LOGV) { - Log.v(LOGTAG, "LoadListener.parseContentTypeHeader: " + - "contentType: " + contentType); - } - - if (contentType != null) { - int i = contentType.indexOf(';'); - if (i >= 0) { - mMimeType = contentType.substring(0, i); - - int j = contentType.indexOf('=', i); - if (j > 0) { - i = contentType.indexOf(';', j); - if (i < j) { - i = contentType.length(); - } - mEncoding = contentType.substring(j + 1, i); - } else { - mEncoding = contentType.substring(i + 1); - } - // Trim excess whitespace. - mEncoding = mEncoding.trim(); - - if (i < contentType.length() - 1) { - // for data: uri the mimeType and encoding have - // the form image/jpeg;base64 or text/plain;charset=utf-8 - // or text/html;charset=utf-8;base64 - mTransferEncoding = contentType.substring(i + 1).trim(); - } - } else { - mMimeType = contentType; - } - - // Trim leading and trailing whitespace - mMimeType = mMimeType.trim(); - - try { - Matcher m = CONTENT_TYPE_PATTERN.matcher(mMimeType); - if (m.find()) { - mMimeType = m.group(1); - } else { - guessMimeType(); - } - } catch (IllegalStateException ex) { - guessMimeType(); - } - } - } - - /** - * @return The HTTP-authentication object or null if there - * is no supported scheme in the header. - * If there are several valid schemes present, we pick the - * strongest one. If there are several schemes of the same - * strength, we pick the one that comes first. - */ - private HttpAuthHeader parseAuthHeader(String header) { - if (header != null) { - int posMax = 256; - int posLen = 0; - int[] pos = new int [posMax]; - - int headerLen = header.length(); - if (headerLen > 0) { - // first, we find all unquoted instances of 'Basic' and 'Digest' - boolean quoted = false; - for (int i = 0; i < headerLen && posLen < posMax; ++i) { - if (header.charAt(i) == '\"') { - quoted = !quoted; - } else { - if (!quoted) { - if (header.regionMatches(true, i, - HttpAuthHeader.BASIC_TOKEN, 0, - HttpAuthHeader.BASIC_TOKEN.length())) { - pos[posLen++] = i; - continue; - } - - if (header.regionMatches(true, i, - HttpAuthHeader.DIGEST_TOKEN, 0, - HttpAuthHeader.DIGEST_TOKEN.length())) { - pos[posLen++] = i; - continue; - } - } - } - } - } - - if (posLen > 0) { - // consider all digest schemes first (if any) - for (int i = 0; i < posLen; i++) { - if (header.regionMatches(true, pos[i], - HttpAuthHeader.DIGEST_TOKEN, 0, - HttpAuthHeader.DIGEST_TOKEN.length())) { - String sub = header.substring(pos[i], - (i + 1 < posLen ? pos[i + 1] : headerLen)); - - HttpAuthHeader rval = new HttpAuthHeader(sub); - if (rval.isSupportedScheme()) { - // take the first match - return rval; - } - } - } - - // ...then consider all basic schemes (if any) - for (int i = 0; i < posLen; i++) { - if (header.regionMatches(true, pos[i], - HttpAuthHeader.BASIC_TOKEN, 0, - HttpAuthHeader.BASIC_TOKEN.length())) { - String sub = header.substring(pos[i], - (i + 1 < posLen ? pos[i + 1] : headerLen)); - - HttpAuthHeader rval = new HttpAuthHeader(sub); - if (rval.isSupportedScheme()) { - // take the first match - return rval; - } - } - } - } - } - - return null; - } - - /** - * If the content is a redirect or not modified we should not send - * any data into WebCore as that will cause it create a document with - * the data, then when we try to provide the real content, it will assert. - * - * @return True iff the callback should be ignored. - */ - private boolean ignoreCallbacks() { - return (mCancelled || mAuthHeader != null || - (mStatusCode > 300 && mStatusCode < 400)); - } - - /** - * Sets the current URL associated with this load. - */ - void setUrl(String url) { - if (url != null) { - 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 { - mUri = new WebAddress(mUrl); - } catch (ParseException e) { - e.printStackTrace(); - } - } - } - } - - /** - * Guesses MIME type if one was not specified. Defaults to 'text/html'. In - * addition, tries to guess the MIME type based on the extension. - * - */ - private void guessMimeType() { - // Data urls must have a valid mime type or a blank string for the mime - // type (implying text/plain). - if (URLUtil.isDataUrl(mUrl) && mMimeType.length() != 0) { - cancel(); - final String text = mContext.getString(R.string.httpErrorBadUrl); - handleError(EventHandler.ERROR_BAD_URL, text); - } else { - // Note: This is ok because this is used only for the main content - // of frames. If no content-type was specified, it is fine to - // default to text/html. - mMimeType = "text/html"; - String newMimeType = guessMimeTypeFromExtension(); - if (newMimeType != null) { - mMimeType = newMimeType; - } - } - } - - /** - * guess MIME type based on the file extension. - */ - private String guessMimeTypeFromExtension() { - // PENDING: need to normalize url - if (Config.LOGV) { - Log.v(LOGTAG, "guessMimeTypeFromExtension: mURL = " + mUrl); - } - - String mimeType = - MimeTypeMap.getSingleton().getMimeTypeFromExtension( - MimeTypeMap.getFileExtensionFromUrl(mUrl)); - - if (mimeType != null) { - // XXX: Until the servers send us either correct xhtml or - // text/html, treat application/xhtml+xml as text/html. - if (mimeType.equals("application/xhtml+xml")) { - mimeType = "text/html"; - } - } - - return mimeType; - } - - /** - * Either send a message to ourselves or queue the message if this is a - * synchronous load. - */ - private void sendMessageInternal(Message msg) { - if (mSynchronous) { - mMessageQueue.add(msg); - } else { - sendMessage(msg); - } - } - - /** - * Cycle through our messages for synchronous loads. - */ - /* package */ void loadSynchronousMessages() { - if (Config.DEBUG && !mSynchronous) { - throw new AssertionError(); - } - // Note: this can be called twice if it is a synchronous network load, - // and there is a cache, but it needs to go to network to validate. If - // validation succeed, the CacheLoader is used so this is first called - // from http thread. Then it is called again from WebViewCore thread - // after the load is completed. So make sure the queue is cleared but - // don't set it to null. - for (int size = mMessageQueue.size(); size > 0; size--) { - handleMessage(mMessageQueue.remove(0)); - } - } - - //========================================================================= - // native functions - //========================================================================= - - /** - * Create a new native response object. - * @param url The url of the resource. - * @param statusCode The HTTP status code. - * @param statusText The HTTP status text. - * @param mimeType HTTP content-type. - * @param expectedLength An estimate of the content length or the length - * given by the server. - * @param encoding HTTP encoding. - * @param expireTime HTTP expires converted to seconds since the epoch. - * @return The native response pointer. - */ - private native int nativeCreateResponse(String url, int statusCode, - String statusText, String mimeType, long expectedLength, - String encoding, long expireTime); - - /** - * Add a response header to the native object. - * @param nativeResponse The native pointer. - * @param key String key. - * @param val String value. - */ - private native void nativeSetResponseHeader(int nativeResponse, String key, - String val); - - /** - * Dispatch the response. - * @param nativeResponse The native pointer. - */ - private native void nativeReceivedResponse(int nativeResponse); - - /** - * Add data to the loader. - * @param data Byte array of data. - * @param length Number of objects in data. - */ - private native void nativeAddData(byte[] data, int length); - - /** - * Tell the loader it has finished. - */ - private native void nativeFinished(); - - /** - * tell the loader to redirect - * @param baseUrl The base url. - * @param redirectTo The url to redirect to. - * @param nativeResponse The native pointer. - * @return The new url that the resource redirected to. - */ - private native String nativeRedirectedToUrl(String baseUrl, - String redirectTo, int nativeResponse); - - /** - * Tell the loader there is error - * @param id - * @param desc - * @param failingUrl The url that failed. - */ - private native void nativeError(int id, String desc, String failingUrl); - -} diff --git a/core/java/android/webkit/MimeTypeMap.java b/core/java/android/webkit/MimeTypeMap.java deleted file mode 100644 index c9cc208..0000000 --- a/core/java/android/webkit/MimeTypeMap.java +++ /dev/null @@ -1,503 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.webkit; - -import java.util.HashMap; -import java.util.regex.Pattern; - -/** - * Two-way map that maps MIME-types to file extensions and vice versa. - */ -public /* package */ class MimeTypeMap { - - /** - * Singleton MIME-type map instance: - */ - private static MimeTypeMap sMimeTypeMap; - - /** - * MIME-type to file extension mapping: - */ - private HashMap<String, String> mMimeTypeToExtensionMap; - - /** - * File extension to MIME type mapping: - */ - private HashMap<String, String> mExtensionToMimeTypeMap; - - - /** - * Creates a new MIME-type map. - */ - private MimeTypeMap() { - mMimeTypeToExtensionMap = new HashMap<String, String>(); - mExtensionToMimeTypeMap = new HashMap<String, String>(); - } - - /** - * Returns the file extension or an empty string iff there is no - * extension. - */ - public static String getFileExtensionFromUrl(String url) { - if (url != null && url.length() > 0) { - int query = url.lastIndexOf('?'); - if (query > 0) { - url = url.substring(0, query); - } - int filenamePos = url.lastIndexOf('/'); - String filename = - 0 <= filenamePos ? url.substring(filenamePos + 1) : url; - - // 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)) { - int dotPos = filename.lastIndexOf('.'); - if (0 <= dotPos) { - return filename.substring(dotPos + 1); - } - } - } - - return ""; - } - - /** - * Load an entry into the map. This does not check if the item already - * exists, it trusts the caller! - */ - private void loadEntry(String mimeType, String extension, - boolean textType) { - // - // if we have an existing x --> y mapping, we do not want to - // override it with another mapping x --> ? - // this is mostly because of the way the mime-type map below - // is constructed (if a mime type maps to several extensions - // the first extension is considered the most popular and is - // added first; we do not want to overwrite it later). - // - if (!mMimeTypeToExtensionMap.containsKey(mimeType)) { - mMimeTypeToExtensionMap.put(mimeType, extension); - } - - // - // here, we don't want to map extensions to text MIME types; - // otherwise, we will start replacing generic text/plain and - // text/html with text MIME types that our platform does not - // understand. - // - if (!textType) { - mExtensionToMimeTypeMap.put(extension, mimeType); - } - } - - /** - * @return True iff there is a mimeType entry in the map. - */ - public boolean hasMimeType(String mimeType) { - if (mimeType != null && mimeType.length() > 0) { - return mMimeTypeToExtensionMap.containsKey(mimeType); - } - - return false; - } - - /** - * @return The extension for the MIME type or null iff there is none. - */ - public String getMimeTypeFromExtension(String extension) { - if (extension != null && extension.length() > 0) { - return mExtensionToMimeTypeMap.get(extension); - } - - return null; - } - - /** - * @return True iff there is an extension entry in the map. - */ - public boolean hasExtension(String extension) { - if (extension != null && extension.length() > 0) { - return mExtensionToMimeTypeMap.containsKey(extension); - } - - return false; - } - - /** - * @return The MIME type for the extension or null iff there is none. - */ - public String getExtensionFromMimeType(String mimeType) { - if (mimeType != null && mimeType.length() > 0) { - return mMimeTypeToExtensionMap.get(mimeType); - } - - return null; - } - - /** - * @return The singleton instance of the MIME-type map. - */ - public static MimeTypeMap getSingleton() { - if (sMimeTypeMap == null) { - sMimeTypeMap = new MimeTypeMap(); - - // The following table is based on /etc/mime.types data minus - // chemical/* MIME types and MIME types that don't map to any - // file extensions. We also exclude top-level domain names to - // deal with cases like: - // - // mail.google.com/a/google.com - // - // and "active" MIME types (due to potential security issues). - // - // Also, notice that not all data from this table is actually - // added (see loadEntry method for more details). - - sMimeTypeMap.loadEntry("application/andrew-inset", "ez", false); - sMimeTypeMap.loadEntry("application/dsptype", "tsp", false); - sMimeTypeMap.loadEntry("application/futuresplash", "spl", false); - sMimeTypeMap.loadEntry("application/hta", "hta", false); - sMimeTypeMap.loadEntry("application/mac-binhex40", "hqx", false); - sMimeTypeMap.loadEntry("application/mac-compactpro", "cpt", false); - sMimeTypeMap.loadEntry("application/mathematica", "nb", false); - sMimeTypeMap.loadEntry("application/msaccess", "mdb", false); - sMimeTypeMap.loadEntry("application/oda", "oda", false); - sMimeTypeMap.loadEntry("application/ogg", "ogg", false); - sMimeTypeMap.loadEntry("application/pdf", "pdf", false); - sMimeTypeMap.loadEntry("application/pgp-keys", "key", false); - sMimeTypeMap.loadEntry("application/pgp-signature", "pgp", false); - sMimeTypeMap.loadEntry("application/pics-rules", "prf", false); - sMimeTypeMap.loadEntry("application/rar", "rar", false); - sMimeTypeMap.loadEntry("application/rdf+xml", "rdf", false); - sMimeTypeMap.loadEntry("application/rss+xml", "rss", false); - sMimeTypeMap.loadEntry("application/zip", "zip", false); - sMimeTypeMap.loadEntry("application/vnd.android.package-archive", - "apk", false); - sMimeTypeMap.loadEntry("application/vnd.cinderella", "cdy", false); - sMimeTypeMap.loadEntry("application/vnd.ms-pki.stl", "stl", false); - sMimeTypeMap.loadEntry( - "application/vnd.oasis.opendocument.database", "odb", - false); - sMimeTypeMap.loadEntry( - "application/vnd.oasis.opendocument.formula", "odf", - false); - sMimeTypeMap.loadEntry( - "application/vnd.oasis.opendocument.graphics", "odg", - false); - sMimeTypeMap.loadEntry( - "application/vnd.oasis.opendocument.graphics-template", - "otg", false); - sMimeTypeMap.loadEntry( - "application/vnd.oasis.opendocument.image", "odi", false); - sMimeTypeMap.loadEntry( - "application/vnd.oasis.opendocument.spreadsheet", "ods", - false); - sMimeTypeMap.loadEntry( - "application/vnd.oasis.opendocument.spreadsheet-template", - "ots", false); - sMimeTypeMap.loadEntry( - "application/vnd.oasis.opendocument.text", "odt", false); - sMimeTypeMap.loadEntry( - "application/vnd.oasis.opendocument.text-master", "odm", - false); - sMimeTypeMap.loadEntry( - "application/vnd.oasis.opendocument.text-template", "ott", - false); - sMimeTypeMap.loadEntry( - "application/vnd.oasis.opendocument.text-web", "oth", - false); - sMimeTypeMap.loadEntry("application/vnd.rim.cod", "cod", false); - sMimeTypeMap.loadEntry("application/vnd.smaf", "mmf", false); - sMimeTypeMap.loadEntry("application/vnd.stardivision.calc", "sdc", - false); - sMimeTypeMap.loadEntry("application/vnd.stardivision.draw", "sda", - false); - sMimeTypeMap.loadEntry( - "application/vnd.stardivision.impress", "sdd", false); - sMimeTypeMap.loadEntry( - "application/vnd.stardivision.impress", "sdp", false); - sMimeTypeMap.loadEntry("application/vnd.stardivision.math", "smf", - false); - sMimeTypeMap.loadEntry("application/vnd.stardivision.writer", "sdw", - false); - sMimeTypeMap.loadEntry("application/vnd.stardivision.writer", "vor", - false); - sMimeTypeMap.loadEntry( - "application/vnd.stardivision.writer-global", "sgl", false); - sMimeTypeMap.loadEntry("application/vnd.sun.xml.calc", "sxc", - false); - sMimeTypeMap.loadEntry( - "application/vnd.sun.xml.calc.template", "stc", false); - sMimeTypeMap.loadEntry("application/vnd.sun.xml.draw", "sxd", - false); - sMimeTypeMap.loadEntry( - "application/vnd.sun.xml.draw.template", "std", false); - sMimeTypeMap.loadEntry("application/vnd.sun.xml.impress", "sxi", - false); - sMimeTypeMap.loadEntry( - "application/vnd.sun.xml.impress.template", "sti", false); - sMimeTypeMap.loadEntry("application/vnd.sun.xml.math", "sxm", - false); - sMimeTypeMap.loadEntry("application/vnd.sun.xml.writer", "sxw", - false); - sMimeTypeMap.loadEntry( - "application/vnd.sun.xml.writer.global", "sxg", false); - sMimeTypeMap.loadEntry( - "application/vnd.sun.xml.writer.template", "stw", false); - sMimeTypeMap.loadEntry("application/vnd.visio", "vsd", false); - sMimeTypeMap.loadEntry("application/x-abiword", "abw", false); - sMimeTypeMap.loadEntry("application/x-apple-diskimage", "dmg", - false); - sMimeTypeMap.loadEntry("application/x-bcpio", "bcpio", false); - sMimeTypeMap.loadEntry("application/x-bittorrent", "torrent", - false); - sMimeTypeMap.loadEntry("application/x-cdf", "cdf", false); - sMimeTypeMap.loadEntry("application/x-cdlink", "vcd", false); - sMimeTypeMap.loadEntry("application/x-chess-pgn", "pgn", false); - sMimeTypeMap.loadEntry("application/x-cpio", "cpio", false); - sMimeTypeMap.loadEntry("application/x-debian-package", "deb", - false); - sMimeTypeMap.loadEntry("application/x-debian-package", "udeb", - false); - sMimeTypeMap.loadEntry("application/x-director", "dcr", false); - sMimeTypeMap.loadEntry("application/x-director", "dir", false); - sMimeTypeMap.loadEntry("application/x-director", "dxr", false); - sMimeTypeMap.loadEntry("application/x-dms", "dms", false); - sMimeTypeMap.loadEntry("application/x-doom", "wad", false); - sMimeTypeMap.loadEntry("application/x-dvi", "dvi", false); - sMimeTypeMap.loadEntry("application/x-flac", "flac", false); - sMimeTypeMap.loadEntry("application/x-font", "pfa", false); - sMimeTypeMap.loadEntry("application/x-font", "pfb", false); - sMimeTypeMap.loadEntry("application/x-font", "gsf", false); - sMimeTypeMap.loadEntry("application/x-font", "pcf", false); - sMimeTypeMap.loadEntry("application/x-font", "pcf.Z", false); - sMimeTypeMap.loadEntry("application/x-freemind", "mm", false); - sMimeTypeMap.loadEntry("application/x-futuresplash", "spl", false); - sMimeTypeMap.loadEntry("application/x-gnumeric", "gnumeric", false); - sMimeTypeMap.loadEntry("application/x-go-sgf", "sgf", false); - sMimeTypeMap.loadEntry("application/x-graphing-calculator", "gcf", - false); - sMimeTypeMap.loadEntry("application/x-gtar", "gtar", false); - sMimeTypeMap.loadEntry("application/x-gtar", "tgz", false); - sMimeTypeMap.loadEntry("application/x-gtar", "taz", false); - sMimeTypeMap.loadEntry("application/x-hdf", "hdf", false); - sMimeTypeMap.loadEntry("application/x-ica", "ica", false); - sMimeTypeMap.loadEntry("application/x-internet-signup", "ins", - false); - sMimeTypeMap.loadEntry("application/x-internet-signup", "isp", - false); - sMimeTypeMap.loadEntry("application/x-iphone", "iii", false); - sMimeTypeMap.loadEntry("application/x-iso9660-image", "iso", false); - sMimeTypeMap.loadEntry("application/x-jmol", "jmz", false); - sMimeTypeMap.loadEntry("application/x-kchart", "chrt", false); - sMimeTypeMap.loadEntry("application/x-killustrator", "kil", false); - sMimeTypeMap.loadEntry("application/x-koan", "skp", false); - sMimeTypeMap.loadEntry("application/x-koan", "skd", false); - sMimeTypeMap.loadEntry("application/x-koan", "skt", false); - sMimeTypeMap.loadEntry("application/x-koan", "skm", false); - sMimeTypeMap.loadEntry("application/x-kpresenter", "kpr", false); - sMimeTypeMap.loadEntry("application/x-kpresenter", "kpt", false); - sMimeTypeMap.loadEntry("application/x-kspread", "ksp", false); - sMimeTypeMap.loadEntry("application/x-kword", "kwd", false); - sMimeTypeMap.loadEntry("application/x-kword", "kwt", false); - sMimeTypeMap.loadEntry("application/x-latex", "latex", false); - sMimeTypeMap.loadEntry("application/x-lha", "lha", false); - sMimeTypeMap.loadEntry("application/x-lzh", "lzh", false); - sMimeTypeMap.loadEntry("application/x-lzx", "lzx", false); - sMimeTypeMap.loadEntry("application/x-maker", "frm", false); - sMimeTypeMap.loadEntry("application/x-maker", "maker", false); - sMimeTypeMap.loadEntry("application/x-maker", "frame", false); - sMimeTypeMap.loadEntry("application/x-maker", "fb", false); - sMimeTypeMap.loadEntry("application/x-maker", "book", false); - sMimeTypeMap.loadEntry("application/x-maker", "fbdoc", false); - sMimeTypeMap.loadEntry("application/x-mif", "mif", false); - sMimeTypeMap.loadEntry("application/x-ms-wmd", "wmd", false); - sMimeTypeMap.loadEntry("application/x-ms-wmz", "wmz", false); - sMimeTypeMap.loadEntry("application/x-msi", "msi", false); - sMimeTypeMap.loadEntry("application/x-ns-proxy-autoconfig", "pac", - false); - sMimeTypeMap.loadEntry("application/x-nwc", "nwc", false); - sMimeTypeMap.loadEntry("application/x-object", "o", false); - sMimeTypeMap.loadEntry("application/x-oz-application", "oza", - false); - sMimeTypeMap.loadEntry("application/x-pkcs7-certreqresp", "p7r", - false); - sMimeTypeMap.loadEntry("application/x-pkcs7-crl", "crl", false); - sMimeTypeMap.loadEntry("application/x-quicktimeplayer", "qtl", - false); - sMimeTypeMap.loadEntry("application/x-shar", "shar", false); - sMimeTypeMap.loadEntry("application/x-stuffit", "sit", false); - sMimeTypeMap.loadEntry("application/x-sv4cpio", "sv4cpio", false); - sMimeTypeMap.loadEntry("application/x-sv4crc", "sv4crc", false); - sMimeTypeMap.loadEntry("application/x-tar", "tar", false); - sMimeTypeMap.loadEntry("application/x-texinfo", "texinfo", false); - sMimeTypeMap.loadEntry("application/x-texinfo", "texi", false); - sMimeTypeMap.loadEntry("application/x-troff", "t", false); - sMimeTypeMap.loadEntry("application/x-troff", "roff", false); - sMimeTypeMap.loadEntry("application/x-troff-man", "man", false); - sMimeTypeMap.loadEntry("application/x-ustar", "ustar", false); - sMimeTypeMap.loadEntry("application/x-wais-source", "src", false); - sMimeTypeMap.loadEntry("application/x-wingz", "wz", false); - sMimeTypeMap.loadEntry( - "application/x-webarchive", "webarchive", false); // added - sMimeTypeMap.loadEntry("application/x-x509-ca-cert", "crt", false); - sMimeTypeMap.loadEntry("application/x-xcf", "xcf", false); - sMimeTypeMap.loadEntry("application/x-xfig", "fig", false); - sMimeTypeMap.loadEntry("audio/basic", "snd", false); - sMimeTypeMap.loadEntry("audio/midi", "mid", false); - sMimeTypeMap.loadEntry("audio/midi", "midi", false); - sMimeTypeMap.loadEntry("audio/midi", "kar", false); - sMimeTypeMap.loadEntry("audio/mpeg", "mpga", false); - sMimeTypeMap.loadEntry("audio/mpeg", "mpega", false); - sMimeTypeMap.loadEntry("audio/mpeg", "mp2", false); - sMimeTypeMap.loadEntry("audio/mpeg", "mp3", false); - sMimeTypeMap.loadEntry("audio/mpeg", "m4a", false); - sMimeTypeMap.loadEntry("audio/mpegurl", "m3u", false); - sMimeTypeMap.loadEntry("audio/prs.sid", "sid", false); - sMimeTypeMap.loadEntry("audio/x-aiff", "aif", false); - sMimeTypeMap.loadEntry("audio/x-aiff", "aiff", false); - sMimeTypeMap.loadEntry("audio/x-aiff", "aifc", false); - sMimeTypeMap.loadEntry("audio/x-gsm", "gsm", false); - sMimeTypeMap.loadEntry("audio/x-mpegurl", "m3u", false); - sMimeTypeMap.loadEntry("audio/x-ms-wma", "wma", false); - sMimeTypeMap.loadEntry("audio/x-ms-wax", "wax", false); - sMimeTypeMap.loadEntry("audio/x-pn-realaudio", "ra", false); - sMimeTypeMap.loadEntry("audio/x-pn-realaudio", "rm", false); - sMimeTypeMap.loadEntry("audio/x-pn-realaudio", "ram", false); - sMimeTypeMap.loadEntry("audio/x-realaudio", "ra", false); - sMimeTypeMap.loadEntry("audio/x-scpls", "pls", false); - sMimeTypeMap.loadEntry("audio/x-sd2", "sd2", false); - sMimeTypeMap.loadEntry("audio/x-wav", "wav", false); - sMimeTypeMap.loadEntry("image/bmp", "bmp", false); // added - sMimeTypeMap.loadEntry("image/gif", "gif", false); - sMimeTypeMap.loadEntry("image/ico", "cur", false); // added - sMimeTypeMap.loadEntry("image/ico", "ico", false); // added - sMimeTypeMap.loadEntry("image/ief", "ief", false); - sMimeTypeMap.loadEntry("image/jpeg", "jpeg", false); - sMimeTypeMap.loadEntry("image/jpeg", "jpg", false); - sMimeTypeMap.loadEntry("image/jpeg", "jpe", false); - sMimeTypeMap.loadEntry("image/pcx", "pcx", false); - sMimeTypeMap.loadEntry("image/png", "png", false); - sMimeTypeMap.loadEntry("image/svg+xml", "svg", false); - sMimeTypeMap.loadEntry("image/svg+xml", "svgz", false); - sMimeTypeMap.loadEntry("image/tiff", "tiff", false); - sMimeTypeMap.loadEntry("image/tiff", "tif", false); - sMimeTypeMap.loadEntry("image/vnd.djvu", "djvu", false); - sMimeTypeMap.loadEntry("image/vnd.djvu", "djv", false); - sMimeTypeMap.loadEntry("image/vnd.wap.wbmp", "wbmp", false); - sMimeTypeMap.loadEntry("image/x-cmu-raster", "ras", false); - sMimeTypeMap.loadEntry("image/x-coreldraw", "cdr", false); - sMimeTypeMap.loadEntry("image/x-coreldrawpattern", "pat", false); - sMimeTypeMap.loadEntry("image/x-coreldrawtemplate", "cdt", false); - sMimeTypeMap.loadEntry("image/x-corelphotopaint", "cpt", false); - sMimeTypeMap.loadEntry("image/x-icon", "ico", false); - sMimeTypeMap.loadEntry("image/x-jg", "art", false); - sMimeTypeMap.loadEntry("image/x-jng", "jng", false); - sMimeTypeMap.loadEntry("image/x-ms-bmp", "bmp", false); - sMimeTypeMap.loadEntry("image/x-photoshop", "psd", false); - sMimeTypeMap.loadEntry("image/x-portable-anymap", "pnm", false); - sMimeTypeMap.loadEntry("image/x-portable-bitmap", "pbm", false); - sMimeTypeMap.loadEntry("image/x-portable-graymap", "pgm", false); - sMimeTypeMap.loadEntry("image/x-portable-pixmap", "ppm", false); - sMimeTypeMap.loadEntry("image/x-rgb", "rgb", false); - sMimeTypeMap.loadEntry("image/x-xbitmap", "xbm", false); - sMimeTypeMap.loadEntry("image/x-xpixmap", "xpm", false); - sMimeTypeMap.loadEntry("image/x-xwindowdump", "xwd", false); - sMimeTypeMap.loadEntry("model/iges", "igs", false); - sMimeTypeMap.loadEntry("model/iges", "iges", false); - sMimeTypeMap.loadEntry("model/mesh", "msh", false); - sMimeTypeMap.loadEntry("model/mesh", "mesh", false); - sMimeTypeMap.loadEntry("model/mesh", "silo", false); - sMimeTypeMap.loadEntry("text/calendar", "ics", true); - sMimeTypeMap.loadEntry("text/calendar", "icz", true); - sMimeTypeMap.loadEntry("text/comma-separated-values", "csv", true); - sMimeTypeMap.loadEntry("text/css", "css", true); - sMimeTypeMap.loadEntry("text/h323", "323", true); - sMimeTypeMap.loadEntry("text/iuls", "uls", true); - sMimeTypeMap.loadEntry("text/mathml", "mml", true); - // add it first so it will be the default for ExtensionFromMimeType - sMimeTypeMap.loadEntry("text/plain", "txt", true); - sMimeTypeMap.loadEntry("text/plain", "asc", true); - sMimeTypeMap.loadEntry("text/plain", "text", true); - sMimeTypeMap.loadEntry("text/plain", "diff", true); - sMimeTypeMap.loadEntry("text/plain", "pot", true); - sMimeTypeMap.loadEntry("text/richtext", "rtx", true); - sMimeTypeMap.loadEntry("text/rtf", "rtf", true); - sMimeTypeMap.loadEntry("text/texmacs", "ts", true); - sMimeTypeMap.loadEntry("text/text", "phps", true); - sMimeTypeMap.loadEntry("text/tab-separated-values", "tsv", true); - sMimeTypeMap.loadEntry("text/x-bibtex", "bib", true); - sMimeTypeMap.loadEntry("text/x-boo", "boo", true); - sMimeTypeMap.loadEntry("text/x-c++hdr", "h++", true); - sMimeTypeMap.loadEntry("text/x-c++hdr", "hpp", true); - sMimeTypeMap.loadEntry("text/x-c++hdr", "hxx", true); - sMimeTypeMap.loadEntry("text/x-c++hdr", "hh", true); - sMimeTypeMap.loadEntry("text/x-c++src", "c++", true); - sMimeTypeMap.loadEntry("text/x-c++src", "cpp", true); - sMimeTypeMap.loadEntry("text/x-c++src", "cxx", true); - sMimeTypeMap.loadEntry("text/x-chdr", "h", true); - sMimeTypeMap.loadEntry("text/x-component", "htc", true); - sMimeTypeMap.loadEntry("text/x-csh", "csh", true); - sMimeTypeMap.loadEntry("text/x-csrc", "c", true); - sMimeTypeMap.loadEntry("text/x-dsrc", "d", true); - sMimeTypeMap.loadEntry("text/x-haskell", "hs", true); - sMimeTypeMap.loadEntry("text/x-java", "java", true); - sMimeTypeMap.loadEntry("text/x-literate-haskell", "lhs", true); - sMimeTypeMap.loadEntry("text/x-moc", "moc", true); - sMimeTypeMap.loadEntry("text/x-pascal", "p", true); - sMimeTypeMap.loadEntry("text/x-pascal", "pas", true); - sMimeTypeMap.loadEntry("text/x-pcs-gcd", "gcd", true); - sMimeTypeMap.loadEntry("text/x-setext", "etx", true); - sMimeTypeMap.loadEntry("text/x-tcl", "tcl", true); - sMimeTypeMap.loadEntry("text/x-tex", "tex", true); - sMimeTypeMap.loadEntry("text/x-tex", "ltx", true); - sMimeTypeMap.loadEntry("text/x-tex", "sty", true); - sMimeTypeMap.loadEntry("text/x-tex", "cls", true); - sMimeTypeMap.loadEntry("text/x-vcalendar", "vcs", true); - sMimeTypeMap.loadEntry("text/x-vcard", "vcf", true); - sMimeTypeMap.loadEntry("video/3gpp", "3gp", false); - sMimeTypeMap.loadEntry("video/3gpp", "3g2", false); - sMimeTypeMap.loadEntry("video/dl", "dl", false); - sMimeTypeMap.loadEntry("video/dv", "dif", false); - sMimeTypeMap.loadEntry("video/dv", "dv", false); - sMimeTypeMap.loadEntry("video/fli", "fli", false); - sMimeTypeMap.loadEntry("video/mpeg", "mpeg", false); - sMimeTypeMap.loadEntry("video/mpeg", "mpg", false); - sMimeTypeMap.loadEntry("video/mpeg", "mpe", false); - sMimeTypeMap.loadEntry("video/mp4", "mp4", false); - sMimeTypeMap.loadEntry("video/mpeg", "VOB", false); - sMimeTypeMap.loadEntry("video/quicktime", "qt", false); - sMimeTypeMap.loadEntry("video/quicktime", "mov", false); - sMimeTypeMap.loadEntry("video/vnd.mpegurl", "mxu", false); - sMimeTypeMap.loadEntry("video/x-la-asf", "lsf", false); - sMimeTypeMap.loadEntry("video/x-la-asf", "lsx", false); - sMimeTypeMap.loadEntry("video/x-mng", "mng", false); - sMimeTypeMap.loadEntry("video/x-ms-asf", "asf", false); - sMimeTypeMap.loadEntry("video/x-ms-asf", "asx", false); - sMimeTypeMap.loadEntry("video/x-ms-wm", "wm", false); - sMimeTypeMap.loadEntry("video/x-ms-wmv", "wmv", false); - sMimeTypeMap.loadEntry("video/x-ms-wmx", "wmx", false); - sMimeTypeMap.loadEntry("video/x-ms-wvx", "wvx", false); - sMimeTypeMap.loadEntry("video/x-msvideo", "avi", false); - sMimeTypeMap.loadEntry("video/x-sgi-movie", "movie", false); - sMimeTypeMap.loadEntry("x-conference/x-cooltalk", "ice", false); - } - - return sMimeTypeMap; - } -} diff --git a/core/java/android/webkit/Network.java b/core/java/android/webkit/Network.java deleted file mode 100644 index 74622b3..0000000 --- a/core/java/android/webkit/Network.java +++ /dev/null @@ -1,349 +0,0 @@ -/* - * Copyright (C) 2006 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.webkit; - -import android.content.Context; -import android.net.http.*; -import android.os.*; -import android.util.Log; -import android.util.Config; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.util.Map; - -import junit.framework.Assert; - -class Network { - - private static final String LOGTAG = "network"; - - /** - * Static instance of a Network object. - */ - private static Network sNetwork; - - /** - * Flag to store the state of platform notifications, for the case - * when the Network object has not been constructed yet - */ - private static boolean sPlatformNotifications; - - /** - * Reference count for platform notifications as the network class is a - * static and can exist over multiple activities, thus over multiple - * onPause/onResume pairs. - */ - private static int sPlatformNotificationEnableRefCount; - - /** - * Proxy username if known (used for pre-emptive proxy authentication). - */ - private String mProxyUsername; - - /** - * Proxy password if known (used for pre-emptive proxy authentication). - */ - private String mProxyPassword; - - /** - * Network request queue (requests are added from the browser thread). - */ - private RequestQueue mRequestQueue; - - /** - * SSL error handler: takes care of synchronization of multiple async - * loaders with SSL-related problems. - */ - private SslErrorHandler mSslErrorHandler; - - /** - * HTTP authentication handler: takes care of synchronization of HTTP - * authentication requests. - */ - private HttpAuthHandler mHttpAuthHandler; - - /** - * @return The singleton instance of the network. - */ - public static synchronized Network getInstance(Context context) { - if (sNetwork == null) { - // Note Context of the Application is used here, rather than - // the what is passed in (usually a Context derived from an - // Activity) so the intent receivers belong to the application - // rather than an activity - this fixes the issue where - // Activities are created and destroyed during the lifetime of - // an Application - sNetwork = new Network(context.getApplicationContext()); - if (sPlatformNotifications) { - // Adjust the ref count before calling enable as it is already - // taken into account when the static function was called - // directly - --sPlatformNotificationEnableRefCount; - enablePlatformNotifications(); - } - } - return sNetwork; - } - - - /** - * Enables data state and proxy tracking - */ - public static void enablePlatformNotifications() { - if (++sPlatformNotificationEnableRefCount == 1) { - if (sNetwork != null) { - sNetwork.mRequestQueue.enablePlatformNotifications(); - } else { - sPlatformNotifications = true; - } - } - } - - /** - * If platform notifications are enabled, this should be called - * from onPause() or onStop() - */ - public static void disablePlatformNotifications() { - if (--sPlatformNotificationEnableRefCount == 0) { - if (sNetwork != null) { - sNetwork.mRequestQueue.disablePlatformNotifications(); - } else { - sPlatformNotifications = false; - } - } - } - - /** - * Creates a new Network object. - * XXX: Must be created in the same thread as WebCore!!!!! - */ - private Network(Context context) { - if (Config.DEBUG) { - Assert.assertTrue(Thread.currentThread(). - getName().equals(WebViewCore.THREAD_NAME)); - } - mSslErrorHandler = new SslErrorHandler(this); - mHttpAuthHandler = new HttpAuthHandler(this); - - mRequestQueue = new RequestQueue(context); - } - - /** - * Request a url from either the network or the file system. - * @param url The url to load. - * @param method The http method. - * @param headers The http headers. - * @param postData The body of the request. - * @param loader A LoadListener for receiving the results of the request. - * @param isHighPriority True if this is high priority request. - * @return True if the request was successfully queued. - */ - public boolean requestURL(String method, - Map<String, String> headers, - byte [] postData, - LoadListener loader, - boolean isHighPriority) { - - String url = loader.url(); - - // Not a valid url, return false because we won't service the request! - if (!URLUtil.isValidUrl(url)) { - return false; - } - - // asset, file system or data stream are handled in the other code path. - // This only handles network request. - if (URLUtil.isAssetUrl(url) || URLUtil.isFileUrl(url) || - URLUtil.isDataUrl(url)) { - return false; - } - - /* FIXME: this is lame. Pass an InputStream in, rather than - making this lame one here */ - InputStream bodyProvider = null; - int bodyLength = 0; - if (postData != null) { - bodyLength = postData.length; - bodyProvider = new ByteArrayInputStream(postData); - } - - RequestQueue q = mRequestQueue; - if (loader.isSynchronous()) { - q = new RequestQueue(loader.getContext(), 1); - } - - RequestHandle handle = q.queueRequest( - url, loader.getWebAddress(), method, headers, loader, - bodyProvider, bodyLength, isHighPriority); - loader.attachRequestHandle(handle); - - if (loader.isSynchronous()) { - handle.waitUntilComplete(); - loader.loadSynchronousMessages(); - q.shutdown(); - } - return true; - } - - /** - * @return True iff there is a valid proxy set. - */ - public boolean isValidProxySet() { - // The proxy host and port can be set within a different thread during - // an Intent broadcast. - synchronized (mRequestQueue) { - return mRequestQueue.getProxyHost() != null; - } - } - - /** - * Get the proxy hostname. - * @return The proxy hostname obtained from the network queue and proxy - * settings. - */ - public String getProxyHostname() { - return mRequestQueue.getProxyHost().getHostName(); - } - - /** - * @return The proxy username or null if none. - */ - public synchronized String getProxyUsername() { - return mProxyUsername; - } - - /** - * Sets the proxy username. - * @param proxyUsername Username to use when - * connecting through the proxy. - */ - public synchronized void setProxyUsername(String proxyUsername) { - if (Config.DEBUG) { - Assert.assertTrue(isValidProxySet()); - } - - mProxyUsername = proxyUsername; - } - - /** - * @return The proxy password or null if none. - */ - public synchronized String getProxyPassword() { - return mProxyPassword; - } - - /** - * Sets the proxy password. - * @param proxyPassword Password to use when - * connecting through the proxy. - */ - public synchronized void setProxyPassword(String proxyPassword) { - if (Config.DEBUG) { - Assert.assertTrue(isValidProxySet()); - } - - mProxyPassword = proxyPassword; - } - - /** - * If we need to stop loading done in a handler (here, browser frame), we - * send a message to the handler to stop loading, and remove all loaders - * that share the same CallbackProxy in question from all local - * handlers (such as ssl-error and http-authentication handler). - * @param proxy The CallbackProxy responsible for cancelling the current - * load. - */ - public void resetHandlersAndStopLoading(BrowserFrame frame) { - if (Config.LOGV) { - Log.v(LOGTAG, "Network.resetHandlersAndStopLoading()"); - } - - frame.stopLoading(); - mSslErrorHandler.reset(frame); - mHttpAuthHandler.reset(frame); - } - - /** - * Saves the state of network handlers (user SSL and HTTP-authentication - * preferences). - * @param outState The out-state to save (write) to. - * @return True iff succeeds. - */ - public boolean saveState(Bundle outState) { - if (Config.LOGV) { - Log.v(LOGTAG, "Network.saveState()"); - } - - return mSslErrorHandler.saveState(outState); - } - - /** - * Restores the state of network handlers (user SSL and HTTP-authentication - * preferences). - * @param inState The in-state to load (read) from. - * @return True iff succeeds. - */ - public boolean restoreState(Bundle inState) { - if (Config.LOGV) { - Log.v(LOGTAG, "Network.restoreState()"); - } - - return mSslErrorHandler.restoreState(inState); - } - - /** - * Clears user SSL-error preference table. - */ - public void clearUserSslPrefTable() { - mSslErrorHandler.clear(); - } - - /** - * Handles SSL error(s) on the way up to the user: the user must decide - * whether errors should be ignored or not. - * @param loader The loader that resulted in SSL errors. - */ - public void handleSslErrorRequest(LoadListener loader) { - if (Config.DEBUG) Assert.assertNotNull(loader); - if (loader != null) { - mSslErrorHandler.handleSslErrorRequest(loader); - } - } - - /** - * Handles authentication requests on their way up to the user (the user - * must provide credentials). - * @param loader The loader that resulted in an HTTP - * authentication request. - */ - public void handleAuthRequest(LoadListener loader) { - if (Config.DEBUG) Assert.assertNotNull(loader); - if (loader != null) { - mHttpAuthHandler.handleAuthRequest(loader); - } - } - - // Performance probe - public void startTiming() { - mRequestQueue.startTiming(); - } - - public void stopTiming() { - mRequestQueue.stopTiming(); - } -} diff --git a/core/java/android/webkit/PerfChecker.java b/core/java/android/webkit/PerfChecker.java deleted file mode 100644 index 8c5f86e..0000000 --- a/core/java/android/webkit/PerfChecker.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.webkit; - -import android.os.SystemClock; -import android.util.Log; - -class PerfChecker { - - private long mTime; - private static final long mResponseThreshold = 2000; // 2s - - public PerfChecker() { - if (false) { - mTime = SystemClock.uptimeMillis(); - } - } - - /** - * @param what log string - * Logs given string if mResponseThreshold time passed between either - * instantiation or previous responseAlert call - */ - public void responseAlert(String what) { - if (false) { - long upTime = SystemClock.uptimeMillis(); - long time = upTime - mTime; - if (time > mResponseThreshold) { - Log.w("webkit", what + " used " + time + " ms"); - } - // Reset mTime, to permit reuse - mTime = upTime; - } - } -} diff --git a/core/java/android/webkit/Plugin.java b/core/java/android/webkit/Plugin.java deleted file mode 100644 index f83da99..0000000 --- a/core/java/android/webkit/Plugin.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.webkit; - -import com.android.internal.R; - -import android.app.AlertDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.webkit.WebView; - -/** - * Represents a plugin (Java equivalent of the PluginPackageAndroid - * C++ class in libs/WebKitLib/WebKit/WebCore/plugins/android/) - */ -public class Plugin { - public interface PreferencesClickHandler { - public void handleClickEvent(Context context); - } - - private String mName; - private String mPath; - private String mFileName; - private String mDescription; - private PreferencesClickHandler mHandler; - - public Plugin(String name, - String path, - String fileName, - String description) { - mName = name; - mPath = path; - mFileName = fileName; - mDescription = description; - mHandler = new DefaultClickHandler(); - } - - public String toString() { - return mName; - } - - public String getName() { - return mName; - } - - public String getPath() { - return mPath; - } - - public String getFileName() { - return mFileName; - } - - public String getDescription() { - return mDescription; - } - - public void setName(String name) { - mName = name; - } - - public void setPath(String path) { - mPath = path; - } - - public void setFileName(String fileName) { - mFileName = fileName; - } - - public void setDescription(String description) { - mDescription = description; - } - - public void setClickHandler(PreferencesClickHandler handler) { - mHandler = handler; - } - - /** - * Invokes the click handler for this plugin. - */ - public void dispatchClickEvent(Context context) { - if (mHandler != null) { - mHandler.handleClickEvent(context); - } - } - - /** - * Default click handler. The plugins should implement their own. - */ - private class DefaultClickHandler implements PreferencesClickHandler, - DialogInterface.OnClickListener { - private AlertDialog mDialog; - - public void handleClickEvent(Context context) { - // Show a simple popup dialog containing the description - // string of the plugin. - if (mDialog == null) { - mDialog = new AlertDialog.Builder(context) - .setTitle(mName) - .setMessage(mDescription) - .setPositiveButton(R.string.ok, this) - .setCancelable(false) - .show(); - } - } - - public void onClick(DialogInterface dialog, int which) { - mDialog.dismiss(); - mDialog = null; - } - } -} diff --git a/core/java/android/webkit/PluginList.java b/core/java/android/webkit/PluginList.java deleted file mode 100644 index a9d3d8c..0000000 --- a/core/java/android/webkit/PluginList.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.webkit; - -import android.content.Context; -import java.util.ArrayList; -import java.util.List; - -/** - * A simple list of initialized plugins. This list gets - * populated when the plugins are initialized (at - * browser startup, at the moment). - */ -public class PluginList { - private ArrayList<Plugin> mPlugins; - - /** - * Public constructor. Initializes the list of plugins. - */ - public PluginList() { - mPlugins = new ArrayList<Plugin>(); - } - - /** - * Returns the list of plugins as a java.util.List. - */ - public synchronized List getList() { - return mPlugins; - } - - /** - * Adds a plugin to the list. - */ - public synchronized void addPlugin(Plugin plugin) { - if (!mPlugins.contains(plugin)) { - mPlugins.add(plugin); - } - } - - /** - * Removes a plugin from the list. - */ - public synchronized void removePlugin(Plugin plugin) { - int location = mPlugins.indexOf(plugin); - if (location != -1) { - mPlugins.remove(location); - } - } - - /** - * Clears the plugin list. - */ - public synchronized void clear() { - mPlugins.clear(); - } - - /** - * Dispatches the click event to the appropriate plugin. - */ - public synchronized void pluginClicked(Context context, int position) { - try { - Plugin plugin = mPlugins.get(position); - plugin.dispatchClickEvent(context); - } catch (IndexOutOfBoundsException e) { - // This can happen if the list of plugins - // gets changed while the pref menu is up. - } - } -} diff --git a/core/java/android/webkit/SslErrorHandler.java b/core/java/android/webkit/SslErrorHandler.java deleted file mode 100644 index 115434a..0000000 --- a/core/java/android/webkit/SslErrorHandler.java +++ /dev/null @@ -1,255 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.webkit; - -import junit.framework.Assert; - -import android.net.http.SslError; -import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.util.Config; -import android.util.Log; - -import java.util.LinkedList; -import java.util.ListIterator; - -/** - * SslErrorHandler: class responsible for handling SSL errors. This class is - * passed as a parameter to BrowserCallback.displaySslErrorDialog and is meant - * to receive the user's response. - */ -public class SslErrorHandler extends Handler { - /* One problem here is that there may potentially be multiple SSL errors - * coming from mutiple loaders. Therefore, we keep a queue of loaders - * that have SSL-related problems and process errors one by one in the - * order they were received. - */ - - private static final String LOGTAG = "network"; - - /** - * Network. - */ - private Network mNetwork; - - /** - * Queue of loaders that experience SSL-related problems. - */ - private LinkedList<LoadListener> mLoaderQueue; - - /** - * SSL error preference table. - */ - private Bundle mSslPrefTable; - - // Message id for handling the response - private final int HANDLE_RESPONSE = 100; - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case HANDLE_RESPONSE: - handleSslErrorResponse(msg.arg1 == 1); - fastProcessQueuedSslErrors(); - break; - } - } - - /** - * Creates a new error handler with an empty loader queue. - */ - /* package */ SslErrorHandler(Network network) { - mNetwork = network; - - mLoaderQueue = new LinkedList<LoadListener>(); - mSslPrefTable = new Bundle(); - } - - /** - * Saves this handler's state into a map. - * @return True iff succeeds. - */ - /* package */ boolean saveState(Bundle outState) { - boolean success = (outState != null); - if (success) { - // TODO? - outState.putBundle("ssl-error-handler", mSslPrefTable); - } - - return success; - } - - /** - * Restores this handler's state from a map. - * @return True iff succeeds. - */ - /* package */ boolean restoreState(Bundle inState) { - boolean success = (inState != null); - if (success) { - success = inState.containsKey("ssl-error-handler"); - if (success) { - mSslPrefTable = inState.getBundle("ssl-error-handler"); - } - } - - return success; - } - - /** - * Clears SSL error preference table. - */ - /* package */ synchronized void clear() { - mSslPrefTable.clear(); - } - - /** - * Resets the SSL error handler, removes all loaders that - * share the same BrowserFrame. - */ - /* package */ synchronized void reset(BrowserFrame frame) { - ListIterator<LoadListener> i = mLoaderQueue.listIterator(0); - while (i.hasNext()) { - LoadListener loader = i.next(); - if (frame == loader.getFrame()) { - i.remove(); - } - } - } - - /** - * Handles SSL error(s) on the way up to the user. - */ - /* package */ synchronized void handleSslErrorRequest(LoadListener loader) { - if (Config.LOGV) { - Log.v(LOGTAG, "SslErrorHandler.handleSslErrorRequest(): " + - "url=" + loader.url()); - } - - if (!loader.cancelled()) { - mLoaderQueue.offer(loader); - if (loader == mLoaderQueue.peek()) { - fastProcessQueuedSslErrors(); - } - } - } - - /** - * Processes queued SSL-error confirmation requests in - * a tight loop while there is no need to ask the user. - */ - /* package */void fastProcessQueuedSslErrors() { - while (processNextLoader()); - } - - /** - * Processes the next loader in the queue. - * @return True iff should proceed to processing the - * following loader in the queue - */ - private synchronized boolean processNextLoader() { - LoadListener loader = mLoaderQueue.peek(); - if (loader != null) { - // if this loader has been cancelled - if (loader.cancelled()) { - // go to the following loader in the queue - return true; - } - - SslError error = loader.sslError(); - - if (Config.DEBUG) { - Assert.assertNotNull(error); - } - - int primary = error.getPrimaryError(); - String host = loader.host(); - - if (Config.DEBUG) { - Assert.assertTrue(host != null && primary != 0); - } - - if (mSslPrefTable.containsKey(host)) { - if (primary <= mSslPrefTable.getInt(host)) { - handleSslErrorResponse(true); - return true; - } - } - - // if we do not have information on record, ask - // the user (display a dialog) - CallbackProxy proxy = loader.getFrame().getCallbackProxy(); - proxy.onReceivedSslError(this, error); - } - - // the queue must be empty, stop - return false; - } - - /** - * Proceed with the SSL certificate. - */ - public void proceed() { - sendMessage(obtainMessage(HANDLE_RESPONSE, 1, 0)); - } - - /** - * Cancel this request and all pending requests for the WebView that had - * the error. - */ - public void cancel() { - sendMessage(obtainMessage(HANDLE_RESPONSE, 0, 0)); - } - - /** - * Handles SSL error(s) on the way down from the user. - */ - /* package */ synchronized void handleSslErrorResponse(boolean proceed) { - LoadListener loader = mLoaderQueue.poll(); - if (Config.DEBUG) { - Assert.assertNotNull(loader); - } - - if (Config.LOGV) { - Log.v(LOGTAG, "SslErrorHandler.handleSslErrorResponse():" - + " proceed: " + proceed - + " url:" + loader.url()); - } - - if (!loader.cancelled()) { - if (proceed) { - // update the user's SSL error preference table - int primary = loader.sslError().getPrimaryError(); - String host = loader.host(); - - if (Config.DEBUG) { - Assert.assertTrue(host != null && primary != 0); - } - boolean hasKey = mSslPrefTable.containsKey(host); - if (!hasKey || - primary > mSslPrefTable.getInt(host)) { - mSslPrefTable.putInt(host, new Integer(primary)); - } - - loader.handleSslErrorResponse(proceed); - } else { - loader.handleSslErrorResponse(proceed); - mNetwork.resetHandlersAndStopLoading(loader.getFrame()); - } - } - } -} diff --git a/core/java/android/webkit/StreamLoader.java b/core/java/android/webkit/StreamLoader.java deleted file mode 100644 index 9098307..0000000 --- a/core/java/android/webkit/StreamLoader.java +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.webkit; - -import android.net.http.EventHandler; -import android.net.http.Headers; -import android.os.Handler; -import android.os.Message; -import android.util.Config; - -import java.io.IOException; -import java.io.InputStream; - - -/** - * This abstract class is used for all content loaders that rely on streaming - * content into the rendering engine loading framework. - * - * The class implements a state machine to load the content into the frame in - * a similar manor to the way content arrives from the network. The class uses - * messages to move from one state to the next, which enables async. loading of - * the streamed content. - * - * Classes that inherit from this class must implement two methods, the first - * method is used to setup the InputStream and notify the loading framework if - * it can load it's content. The other method allows the derived class to add - * additional HTTP headers to the response. - * - * By default, content loaded with a StreamLoader is marked with a HTTP header - * that indicates the content should not be cached. - * - */ -abstract class StreamLoader extends Handler { - - public static final String NO_STORE = "no-store"; - - private static final int MSG_STATUS = 100; // Send status to loader - private static final int MSG_HEADERS = 101; // Send headers to loader - private static final int MSG_DATA = 102; // Send data to loader - private static final int MSG_END = 103; // Send endData to loader - - protected LoadListener mHandler; // loader class - protected InputStream mDataStream; // stream to read data from - protected long mContentLength; // content length of data - private byte [] mData; // buffer to pass data to loader with. - - /** - * Constructor. Although this class calls the LoadListener, it only calls - * the EventHandler Interface methods. LoadListener concrete class is used - * to avoid the penality of calling an interface. - * - * @param loadlistener The LoadListener to call with the data. - */ - StreamLoader(LoadListener loadlistener) { - mHandler = loadlistener; - } - - /** - * This method is called when the derived class should setup mDataStream, - * and call mHandler.status() to indicate that the load can occur. If it - * fails to setup, it should still call status() with the error code. - * - * @return true if stream was successfully setup - */ - protected abstract boolean setupStreamAndSendStatus(); - - /** - * This method is called when the headers are about to be sent to the - * load framework. The derived class has the opportunity to add addition - * headers. - * - * @param headers Map of HTTP headers that will be sent to the loader. - */ - abstract protected void buildHeaders(Headers headers); - - - /** - * Calling this method starts the load of the content for this StreamLoader. - * This method simply posts a message to send the status and returns - * immediately. - */ - public void load() { - if (!mHandler.isSynchronous()) { - sendMessage(obtainMessage(MSG_STATUS)); - } else { - // Load the stream synchronously. - if (setupStreamAndSendStatus()) { - // We were able to open the stream, create the array - // to pass data to the loader - mData = new byte[8192]; - sendHeaders(); - while (!sendData()); - closeStreamAndSendEndData(); - mHandler.loadSynchronousMessages(); - } - } - } - - /* (non-Javadoc) - * @see android.os.Handler#handleMessage(android.os.Message) - */ - public void handleMessage(Message msg) { - if (Config.DEBUG && mHandler.isSynchronous()) { - throw new AssertionError(); - } - switch(msg.what) { - case MSG_STATUS: - if (setupStreamAndSendStatus()) { - // We were able to open the stream, create the array - // to pass data to the loader - mData = new byte[8192]; - sendMessage(obtainMessage(MSG_HEADERS)); - } - break; - case MSG_HEADERS: - sendHeaders(); - sendMessage(obtainMessage(MSG_DATA)); - break; - case MSG_DATA: - if (sendData()) { - sendMessage(obtainMessage(MSG_END)); - } else { - sendMessage(obtainMessage(MSG_DATA)); - } - break; - case MSG_END: - closeStreamAndSendEndData(); - break; - default: - super.handleMessage(msg); - break; - } - } - - /** - * Construct the headers and pass them to the EventHandler. - */ - private void sendHeaders() { - Headers headers = new Headers(); - if (mContentLength > 0) { - headers.setContentLength(mContentLength); - } - headers.setCacheControl(NO_STORE); - buildHeaders(headers); - mHandler.headers(headers); - } - - /** - * Read data from the stream and pass it to the EventHandler. - * If an error occurs reading the stream, then an error is sent to the - * EventHandler, and moves onto the next state - end of data. - * @return True if all the data has been read. False if sendData should be - * called again. - */ - private boolean sendData() { - if (mDataStream != null) { - try { - int amount = mDataStream.read(mData); - if (amount > 0) { - mHandler.data(mData, amount); - return false; - } - } catch (IOException ex) { - mHandler.error(EventHandler.FILE_ERROR, - ex.getMessage()); - } - } - return true; - } - - /** - * Close the stream and inform the EventHandler that load is complete. - */ - private void closeStreamAndSendEndData() { - if (mDataStream != null) { - try { - mDataStream.close(); - } catch (IOException ex) { - // ignore. - } - } - mHandler.endData(); - } - -} diff --git a/core/java/android/webkit/TextDialog.java b/core/java/android/webkit/TextDialog.java deleted file mode 100644 index c2620a5..0000000 --- a/core/java/android/webkit/TextDialog.java +++ /dev/null @@ -1,653 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.webkit; - -import android.content.Context; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.Rect; -import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.LayerDrawable; -import android.graphics.drawable.ShapeDrawable; -import android.graphics.drawable.shapes.RectShape; -import android.os.Handler; -import android.os.Message; -import android.text.Editable; -import android.text.InputFilter; -import android.text.Selection; -import android.text.Spannable; -import android.text.TextPaint; -import android.text.TextUtils; -import android.text.method.MovementMethod; -import android.text.method.PasswordTransformationMethod; -import android.text.method.TextKeyListener; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputMethodManager; -import android.view.KeyCharacterMap; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewConfiguration; -import android.widget.AbsoluteLayout.LayoutParams; -import android.widget.ArrayAdapter; -import android.widget.AutoCompleteTextView; -import android.widget.TextView; - -import java.util.ArrayList; - -/** - * TextDialog is a specialized version of EditText used by WebView - * to overlay html textfields (and textareas) to use our standard - * text editing. - */ -/* package */ class TextDialog extends AutoCompleteTextView { - - private WebView mWebView; - private boolean mSingle; - private int mWidthSpec; - private int mHeightSpec; - private int mNodePointer; - // FIXME: This is a hack for blocking unmatched key ups, in particular - // on the enter key. The method for blocking unmatched key ups prevents - // the shift key from working properly. - private boolean mGotEnterDown; - // mScrollToAccommodateCursor being set to false prevents us from scrolling - // the cursor on screen when using the trackball to select a textfield. - private boolean mScrollToAccommodateCursor; - private int mMaxLength; - // Keep track of the text before the change so we know whether we actually - // need to send down the DOM events. - private String mPreChange; - // Array to store the final character added in onTextChanged, so that its - // KeyEvents may be determined. - private char[] mCharacter = new char[1]; - // This is used to reset the length filter when on a textfield - // with no max length. - // FIXME: This can be replaced with TextView.NO_FILTERS if that - // is made public/protected. - private static final InputFilter[] NO_FILTERS = new InputFilter[0]; - // The time of the last enter down, so we know whether to perform a long - // press. - private long mDownTime; - - private boolean mTrackballDown = false; - private static int LONGPRESS = 1; - private Handler mHandler = new Handler() { - public void handleMessage(Message msg) { - if (msg.what == LONGPRESS) { - if (mTrackballDown) { - performLongClick(); - mTrackballDown = false; - } - } - } - }; - - /** - * Create a new TextDialog. - * @param context The Context for this TextDialog. - * @param webView The WebView that created this. - */ - /* package */ TextDialog(Context context, WebView webView) { - super(context); - mWebView = webView; - ShapeDrawable background = new ShapeDrawable(new RectShape()); - Paint shapePaint = background.getPaint(); - shapePaint.setStyle(Paint.Style.STROKE); - ColorDrawable color = new ColorDrawable(Color.WHITE); - Drawable[] array = new Drawable[2]; - array[0] = color; - array[1] = background; - LayerDrawable layers = new LayerDrawable(array); - // Hide WebCore's text behind this and allow the WebView - // to draw its own focusring. - setBackgroundDrawable(layers); - // Align the text better with the text behind it, so moving - // off of the textfield will not appear to move the text. - setPadding(3, 2, 0, 0); - mMaxLength = -1; - // Turn on subpixel text, and turn off kerning, so it better matches - // the text in webkit. - TextPaint paint = getPaint(); - 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 - public boolean dispatchKeyEvent(KeyEvent event) { - if (event.isSystem()) { - return super.dispatchKeyEvent(event); - } - // Treat ACTION_DOWN and ACTION MULTIPLE the same - boolean down = event.getAction() != KeyEvent.ACTION_UP; - int keyCode = event.getKeyCode(); - Spannable text = (Spannable) getText(); - int oldLength = text.length(); - // Normally the delete key's dom events are sent via onTextChanged. - // However, if the length is zero, the text did not change, so we - // go ahead and pass the key down immediately. - if (KeyEvent.KEYCODE_DEL == keyCode && 0 == oldLength) { - sendDomEvent(event); - return true; - } - - // For single-line textfields, return key should not be handled - // here. Instead, the WebView is passed the key up, so it may fire a - // submit/onClick. - // Center key should always be passed to a potential onClick - if ((mSingle && KeyEvent.KEYCODE_ENTER == keyCode) - || KeyEvent.KEYCODE_DPAD_CENTER == keyCode) { - if (isPopupShowing()) { - super.dispatchKeyEvent(event); - return true; - } - if (down) { - if (event.getRepeatCount() == 0) { - mGotEnterDown = true; - mDownTime = event.getEventTime(); - // Send the keydown when the up comes, so that we have - // a chance to handle a long press. - } else if (mGotEnterDown && event.getEventTime() - mDownTime > - ViewConfiguration.getLongPressTimeout()) { - performLongClick(); - mGotEnterDown = false; - } - } else if (mGotEnterDown) { - mGotEnterDown = false; - if (KeyEvent.KEYCODE_DPAD_CENTER == keyCode) { - mWebView.shortPressOnTextField(); - return true; - } - // If we reached here, then this is a single line textfield, and - // the user pressed ENTER. In this case, we want to hide the - // soft input method. - InputMethodManager.getInstance(mContext) - .hideSoftInputFromWindow(getWindowToken(), 0); - sendDomEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode)); - sendDomEvent(event); - } - return true; - } - // Ensure there is a layout so arrow keys are handled properly. - if (getLayout() == null) { - measure(mWidthSpec, mHeightSpec); - } - int oldStart = Selection.getSelectionStart(text); - int oldEnd = Selection.getSelectionEnd(text); - - boolean maxedOut = mMaxLength != -1 && oldLength == mMaxLength; - // If we are at max length, and there is a selection rather than a - // cursor, we need to store the text to compare later, since the key - // may have changed the string. - String oldText; - if (maxedOut && oldEnd != oldStart) { - oldText = text.toString(); - } else { - oldText = ""; - } - if (super.dispatchKeyEvent(event)) { - // If the TextDialog handled the key it was either an alphanumeric - // key, a delete, or a movement within the text. All of those are - // ok to pass to javascript. - - // UNLESS there is a max length determined by the html. In that - // case, if the string was already at the max length, an - // alphanumeric key will be erased by the LengthFilter, - // so do not pass down to javascript, and instead - // return true. If it is an arrow key or a delete key, we can go - // ahead and pass it down. - boolean isArrowKey; - switch(keyCode) { - case KeyEvent.KEYCODE_DPAD_LEFT: - case KeyEvent.KEYCODE_DPAD_RIGHT: - case KeyEvent.KEYCODE_DPAD_UP: - case KeyEvent.KEYCODE_DPAD_DOWN: - isArrowKey = true; - break; - case KeyEvent.KEYCODE_DPAD_CENTER: - case KeyEvent.KEYCODE_ENTER: - // For multi-line text boxes, newlines and dpad center will - // trigger onTextChanged for key down (which will send both - // key up and key down) but not key up. - mGotEnterDown = true; - default: - isArrowKey = false; - break; - } - if (maxedOut && !isArrowKey && keyCode != KeyEvent.KEYCODE_DEL) { - if (oldEnd == oldStart) { - // Return true so the key gets dropped. - mScrollToAccommodateCursor = true; - return true; - } else if (!oldText.equals(getText().toString())) { - // FIXME: This makes the text work properly, but it - // does not pass down the key event, so it may not - // work for a textfield that has the type of - // behavior of GoogleSuggest. That said, it is - // unlikely that a site would combine the two in - // one textfield. - Spannable span = (Spannable) getText(); - int newStart = Selection.getSelectionStart(span); - int newEnd = Selection.getSelectionEnd(span); - mWebView.replaceTextfieldText(0, oldLength, span.toString(), - newStart, newEnd); - mScrollToAccommodateCursor = true; - return true; - } - } - if (isArrowKey) { - // Arrow key does not change the text, but we still want to send - // the DOM events. - sendDomEvent(event); - } - mScrollToAccommodateCursor = true; - return true; - } - // FIXME: TextViews return false for up and down key events even though - // they change the selection. Since we don't want the get out of sync - // with WebCore's notion of the current selection, reset the selection - // to what it was before the key event. - Selection.setSelection(text, oldStart, oldEnd); - // Ignore the key up event for newlines or dpad center. This prevents - // multiple newlines in the native textarea. - if (mGotEnterDown && !down) { - return true; - } - // if it is a navigation key, pass it to WebView - if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT - || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT - || keyCode == KeyEvent.KEYCODE_DPAD_UP - || keyCode == KeyEvent.KEYCODE_DPAD_DOWN) { - // WebView check the trackballtime in onKeyDown to avoid calling - // native from both trackball and key handling. As this is called - // from TextDialog, we always want WebView to check with native. - // Reset trackballtime to ensure it. - mWebView.resetTrackballTime(); - return down ? mWebView.onKeyDown(keyCode, event) : mWebView - .onKeyUp(keyCode, event); - } - return false; - } - - /** - * Create a fake touch up event at (x,y) with respect to this TextDialog. - * This is used by WebView to act as though a touch event which happened - * before we placed the TextDialog actually hit it, so that it can place - * the cursor accordingly. - */ - /* package */ void fakeTouchEvent(float x, float y) { - // We need to ensure that there is a Layout, since the Layout is used - // in determining where to place the cursor. - if (getLayout() == null) { - measure(mWidthSpec, mHeightSpec); - } - // Create a fake touch up, which is used to place the cursor. - MotionEvent ev = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, - x, y, 0); - onTouchEvent(ev); - ev.recycle(); - } - - /** - * Determine whether this TextDialog currently represents the node - * represented by ptr. - * @param ptr Pointer to a node to compare to. - * @return boolean Whether this TextDialog already represents the node - * pointed to by ptr. - */ - /* package */ boolean isSameTextField(int ptr) { - return ptr == mNodePointer; - } - - @Override - public boolean onPreDraw() { - if (getLayout() == null) { - measure(mWidthSpec, mHeightSpec); - } - return super.onPreDraw(); - } - - @Override - protected void onTextChanged(CharSequence s,int start,int before,int count){ - super.onTextChanged(s, start, before, count); - String postChange = s.toString(); - // Prevent calls to setText from invoking onTextChanged (since this will - // mean we are on a different textfield). Also prevent the change when - // going from a textfield with a string of text to one with a smaller - // limit on text length from registering the onTextChanged event. - if (mPreChange == null || mPreChange.equals(postChange) || - (mMaxLength > -1 && mPreChange.length() > mMaxLength && - mPreChange.substring(0, mMaxLength).equals(postChange))) { - return; - } - mPreChange = postChange; - // This was simply a delete or a cut, so just delete the - // selection. - if (before > 0 && 0 == count) { - mWebView.deleteSelection(start, start + before); - // For this and all changes to the text, update our cache - updateCachedTextfield(); - return; - } - // Find the last character being replaced. If it can be represented by - // events, we will pass them to native (after replacing the beginning - // of the changed text), so we can see javascript events. - // Otherwise, replace the text being changed (including the last - // character) in the textfield. - TextUtils.getChars(s, start + count - 1, start + count, mCharacter, 0); - KeyCharacterMap kmap = - KeyCharacterMap.load(KeyCharacterMap.BUILT_IN_KEYBOARD); - KeyEvent[] events = kmap.getEvents(mCharacter); - boolean cannotUseKeyEvents = null == events; - int charactersFromKeyEvents = cannotUseKeyEvents ? 0 : 1; - if (count > 1 || cannotUseKeyEvents) { - String replace = s.subSequence(start, - start + count - charactersFromKeyEvents).toString(); - mWebView.replaceTextfieldText(start, start + before, replace, - start + count - charactersFromKeyEvents, - start + count - charactersFromKeyEvents); - } else { - // This corrects the selection which may have been affected by the - // trackball or auto-correct. - mWebView.setSelection(start, start + before); - } - updateCachedTextfield(); - if (cannotUseKeyEvents) { - return; - } - int length = events.length; - for (int i = 0; i < length; i++) { - // We never send modifier keys to native code so don't send them - // here either. - if (!KeyEvent.isModifierKey(events[i].getKeyCode())) { - sendDomEvent(events[i]); - } - } - } - - @Override - public boolean onTrackballEvent(MotionEvent event) { - if (isPopupShowing()) { - return super.onTrackballEvent(event); - } - int action = event.getAction(); - switch (action) { - case MotionEvent.ACTION_DOWN: - if (!mTrackballDown) { - mTrackballDown = true; - mHandler.sendEmptyMessageDelayed(LONGPRESS, - ViewConfiguration.getLongPressTimeout()); - } - return true; - case MotionEvent.ACTION_UP: - if (mTrackballDown) { - mWebView.shortPressOnTextField(); - mTrackballDown = false; - mHandler.removeMessages(LONGPRESS); - } - return true; - case MotionEvent.ACTION_CANCEL: - mTrackballDown = false; - return true; - case MotionEvent.ACTION_MOVE: - // fall through - } - Spannable text = (Spannable) getText(); - MovementMethod move = getMovementMethod(); - if (move != null && getLayout() != null && - move.onTrackballEvent(this, text, event)) { - // Need to pass down the selection, which has changed. - // FIXME: This should work, but does not, so we set the selection - // in onTextChanged. - //int start = Selection.getSelectionStart(text); - //int end = Selection.getSelectionEnd(text); - //mWebView.setSelection(start, end); - return true; - } - // If the user is in a textfield, and the movement method is not - // handling the trackball events, it means they are at the end of the - // field and continuing to move the trackball. In this case, we should - // not scroll the cursor on screen bc the user may be attempting to - // scroll the page, possibly in the opposite direction of the cursor. - mScrollToAccommodateCursor = false; - return false; - } - - /** - * Remove this TextDialog from its host WebView, and return - * focus to the host. - */ - /* package */ void remove() { - // hide the soft keyboard when the edit text is out of focus - InputMethodManager.getInstance(mContext).hideSoftInputFromWindow( - getWindowToken(), 0); - mHandler.removeMessages(LONGPRESS); - mWebView.removeView(this); - mWebView.requestFocus(); - mScrollToAccommodateCursor = false; - } - - /* package */ void enableScrollOnScreen(boolean enable) { - mScrollToAccommodateCursor = enable; - } - - /* package */ void bringIntoView() { - if (getLayout() != null) { - bringPointIntoView(Selection.getSelectionEnd(getText())); - } - } - - @Override - public boolean requestRectangleOnScreen(Rect rectangle) { - if (mScrollToAccommodateCursor) { - return super.requestRectangleOnScreen(rectangle); - } - return false; - } - - /** - * Send the DOM events for the specified event. - * @param event KeyEvent to be translated into a DOM event. - */ - private void sendDomEvent(KeyEvent event) { - mWebView.passToJavaScript(getText().toString(), event); - } - - /** - * Always use this instead of setAdapter, as this has features specific to - * the TextDialog. - */ - public void setAdapterCustom(AutoCompleteAdapter adapter) { - if (adapter != null) { - setInputType(EditorInfo.TYPE_TEXT_FLAG_AUTO_COMPLETE); - adapter.setTextView(this); - } - super.setAdapter(adapter); - } - - /** - * This is a special version of ArrayAdapter which changes its text size - * to match the text size of its host TextView. - */ - public static class AutoCompleteAdapter extends ArrayAdapter<String> { - private TextView mTextView; - - public AutoCompleteAdapter(Context context, ArrayList<String> entries) { - super(context, com.android.internal.R.layout - .search_dropdown_item_1line, entries); - } - - /** - * {@inheritDoc} - */ - public View getView(int position, View convertView, ViewGroup parent) { - TextView tv = - (TextView) super.getView(position, convertView, parent); - if (tv != null && mTextView != null) { - tv.setTextSize(mTextView.getTextSize()); - } - return tv; - } - - /** - * Set the TextView so we can match its text size. - */ - private void setTextView(TextView tv) { - mTextView = tv; - } - } - - /** - * Determine whether to use the system-wide password disguising method, - * or to use none. - * @param inPassword True if the textfield is a password field. - */ - /* package */ void setInPassword(boolean inPassword) { - PasswordTransformationMethod method; - if (inPassword) { - method = PasswordTransformationMethod.getInstance(); - setInputType(EditorInfo.TYPE_CLASS_TEXT|EditorInfo. - TYPE_TEXT_VARIATION_PASSWORD); - } else { - method = null; - } - setTransformationMethod(method); - } - - /* package */ void setMaxLength(int maxLength) { - mMaxLength = maxLength; - if (-1 == maxLength) { - setFilters(NO_FILTERS); - } else { - setFilters(new InputFilter[] { - new InputFilter.LengthFilter(maxLength) }); - } - } - - /** - * Set the pointer for this node so it can be determined which node this - * TextDialog represents. - * @param ptr Integer representing the pointer to the node which this - * TextDialog represents. - */ - /* package */ void setNodePointer(int ptr) { - mNodePointer = ptr; - } - - /** - * Determine the position and size of TextDialog, and add it to the - * WebView's view heirarchy. All parameters are presumed to be in - * view coordinates. Also requests Focus and sets the cursor to not - * request to be in view. - * @param x x-position of the textfield. - * @param y y-position of the textfield. - * @param width width of the textfield. - * @param height height of the textfield. - */ - /* package */ void setRect(int x, int y, int width, int height) { - LayoutParams lp = (LayoutParams) getLayoutParams(); - if (null == lp) { - lp = new LayoutParams(width, height, x, y); - } else { - lp.x = x; - lp.y = y; - lp.width = width; - lp.height = height; - } - if (getParent() == null) { - mWebView.addView(this, lp); - } else { - setLayoutParams(lp); - } - // Set up a measure spec so a layout can always be recreated. - mWidthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY); - mHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); - requestFocus(); - } - - /** - * Set whether this is a single-line textfield or a multi-line textarea. - * Textfields scroll horizontally, and do not handle the enter key. - * Textareas behave oppositely. - * Do NOT call this after calling setInPassword(true). This will result in - * removing the password input type. - */ - public void setSingleLine(boolean single) { - if (mSingle != single) { - TextKeyListener.Capitalize cap; - int inputType = EditorInfo.TYPE_CLASS_TEXT; - if (single) { - cap = TextKeyListener.Capitalize.NONE; - } else { - cap = TextKeyListener.Capitalize.SENTENCES; - inputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; - } - setKeyListener(TextKeyListener.getInstance(!single, cap)); - mSingle = single; - setHorizontallyScrolling(single); - setInputType(inputType); - } - } - - /** - * Set the text for this TextDialog, and set the selection to (start, end) - * @param text Text to go into this TextDialog. - * @param start Beginning of the selection. - * @param end End of the selection. - */ - /* package */ void setText(CharSequence text, int start, int end) { - mPreChange = text.toString(); - setText(text); - Spannable span = (Spannable) getText(); - int length = span.length(); - if (end > length) { - end = length; - } - if (start < 0) { - start = 0; - } else if (start > length) { - start = length; - } - Selection.setSelection(span, start, end); - } - - /** - * Set the text to the new string, but use the old selection, making sure - * to keep it within the new string. - * @param text The new text to place in the textfield. - */ - /* package */ void setTextAndKeepSelection(String text) { - mPreChange = text.toString(); - Editable edit = (Editable) getText(); - edit.replace(0, edit.length(), text); - updateCachedTextfield(); - } - - /** - * Update the cache to reflect the current text. - */ - /* package */ void updateCachedTextfield() { - mWebView.updateCachedTextfield(getText().toString()); - } -} diff --git a/core/java/android/webkit/URLUtil.java b/core/java/android/webkit/URLUtil.java deleted file mode 100644 index 0e8144e..0000000 --- a/core/java/android/webkit/URLUtil.java +++ /dev/null @@ -1,363 +0,0 @@ -/* - * Copyright (C) 2006 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.webkit; - -import java.io.UnsupportedEncodingException; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import android.net.Uri; -import android.net.ParseException; -import android.net.WebAddress; -import android.util.Config; -import android.util.Log; - -public final class URLUtil { - - private static final String LOGTAG = "webkit"; - - static final String ASSET_BASE = "file:///android_asset/"; - static final String FILE_BASE = "file://"; - static final String PROXY_BASE = "file:///cookieless_proxy/"; - - /** - * Cleans up (if possible) user-entered web addresses - */ - public static String guessUrl(String inUrl) { - - String retVal = inUrl; - WebAddress webAddress; - - Log.v(LOGTAG, "guessURL before queueRequest: " + inUrl); - - if (inUrl.length() == 0) return inUrl; - if (inUrl.startsWith("about:")) return inUrl; - // Do not try to interpret data scheme URLs - if (inUrl.startsWith("data:")) return inUrl; - // Do not try to interpret file scheme URLs - if (inUrl.startsWith("file:")) return inUrl; - // Do not try to interpret javascript scheme URLs - if (inUrl.startsWith("javascript:")) return inUrl; - - // bug 762454: strip period off end of url - if (inUrl.endsWith(".") == true) { - inUrl = inUrl.substring(0, inUrl.length() - 1); - } - - try { - webAddress = new WebAddress(inUrl); - } catch (ParseException ex) { - - if (Config.LOGV) { - Log.v(LOGTAG, "smartUrlFilter: failed to parse url = " + inUrl); - } - return retVal; - } - - // Check host - if (webAddress.mHost.indexOf('.') == -1) { - // no dot: user probably entered a bare domain. try .com - webAddress.mHost = "www." + webAddress.mHost + ".com"; - } - return webAddress.toString(); - } - - public static String composeSearchUrl(String inQuery, String template, - String queryPlaceHolder) { - int placeHolderIndex = template.indexOf(queryPlaceHolder); - if (placeHolderIndex < 0) { - return null; - } - - String query; - StringBuilder buffer = new StringBuilder(); - buffer.append(template.substring(0, placeHolderIndex)); - - try { - query = java.net.URLEncoder.encode(inQuery, "utf-8"); - buffer.append(query); - } catch (UnsupportedEncodingException ex) { - return null; - } - - buffer.append(template.substring( - placeHolderIndex + queryPlaceHolder.length())); - - return buffer.toString(); - } - - public static byte[] decode(byte[] url) throws IllegalArgumentException { - if (url.length == 0) { - return new byte[0]; - } - - // Create a new byte array with the same length to ensure capacity - byte[] tempData = new byte[url.length]; - - int tempCount = 0; - for (int i = 0; i < url.length; i++) { - byte b = url[i]; - if (b == '%') { - if (url.length - i > 2) { - b = (byte) (parseHex(url[i + 1]) * 16 - + parseHex(url[i + 2])); - i += 2; - } else { - throw new IllegalArgumentException("Invalid format"); - } - } - tempData[tempCount++] = b; - } - byte[] retData = new byte[tempCount]; - System.arraycopy(tempData, 0, retData, 0, tempCount); - return retData; - } - - private static int parseHex(byte b) { - if (b >= '0' && b <= '9') return (b - '0'); - if (b >= 'A' && b <= 'F') return (b - 'A' + 10); - if (b >= 'a' && b <= 'f') return (b - 'a' + 10); - - throw new IllegalArgumentException("Invalid hex char '" + b + "'"); - } - - /** - * @return True iff the url is an asset file. - */ - public static boolean isAssetUrl(String url) { - return (null != url) && url.startsWith(ASSET_BASE); - } - - /** - * @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); - } - - /** - * @return True iff the url is a local file. - */ - public static boolean isFileUrl(String url) { - return (null != url) && (url.startsWith(FILE_BASE) && - !url.startsWith(ASSET_BASE) && - !url.startsWith(PROXY_BASE)); - } - - /** - * @return True iff the url is an about: url. - */ - public static boolean isAboutUrl(String url) { - return (null != url) && url.startsWith("about:"); - } - - /** - * @return True iff the url is a data: url. - */ - public static boolean isDataUrl(String url) { - return (null != url) && url.startsWith("data:"); - } - - /** - * @return True iff the url is a javascript: url. - */ - public static boolean isJavaScriptUrl(String url) { - return (null != url) && url.startsWith("javascript:"); - } - - /** - * @return True iff the url is an http: url. - */ - public static boolean isHttpUrl(String url) { - return (null != url) && - (url.length() > 6) && - url.substring(0, 7).equalsIgnoreCase("http://"); - } - - /** - * @return True iff the url is an https: url. - */ - public static boolean isHttpsUrl(String url) { - return (null != url) && - (url.length() > 7) && - url.substring(0, 8).equalsIgnoreCase("https://"); - } - - /** - * @return True iff the url is a network url. - */ - public static boolean isNetworkUrl(String url) { - if (url == null || url.length() == 0) { - return false; - } - return isHttpUrl(url) || isHttpsUrl(url); - } - - /** - * @return True iff the url is a content: url. - */ - public static boolean isContentUrl(String url) { - return (null != url) && url.startsWith("content:"); - } - - /** - * @return True iff the url is valid. - */ - public static boolean isValidUrl(String url) { - if (url == null || url.length() == 0) { - return false; - } - - return (isAssetUrl(url) || - isFileUrl(url) || - isAboutUrl(url) || - isHttpUrl(url) || - isHttpsUrl(url) || - isJavaScriptUrl(url) || - isContentUrl(url)); - } - - /** - * Strips the url of the anchor. - */ - public static String stripAnchor(String url) { - int anchorIndex = url.indexOf('#'); - if (anchorIndex != -1) { - return url.substring(0, anchorIndex); - } - return url; - } - - /** - * Guesses canonical filename that a download would have, using - * the URL and contentDisposition. File extension, if not defined, - * is added based on the mimetype - * @param url Url to the content - * @param contentDisposition Content-Disposition HTTP header or null - * @param mimeType Mime-type of the content or null - * - * @return suggested filename - */ - public static final String guessFileName( - String url, - String contentDisposition, - String mimeType) { - String filename = null; - String extension = null; - - // If we couldn't do anything with the hint, move toward the content disposition - if (filename == null && contentDisposition != null) { - filename = parseContentDisposition(contentDisposition); - if (filename != null) { - int index = filename.lastIndexOf('/') + 1; - if (index > 0) { - filename = filename.substring(index); - } - } - } - - // If all the other http-related approaches failed, use the plain uri - if (filename == null) { - String decodedUrl = Uri.decode(url); - if (decodedUrl != null) { - int queryIndex = decodedUrl.indexOf('?'); - // If there is a query string strip it, same as desktop browsers - if (queryIndex > 0) { - decodedUrl = decodedUrl.substring(0, queryIndex); - } - if (!decodedUrl.endsWith("/")) { - int index = decodedUrl.lastIndexOf('/') + 1; - if (index > 0) { - filename = decodedUrl.substring(index); - } - } - } - } - - // Finally, if couldn't get filename from URI, get a generic filename - if (filename == null) { - filename = "downloadfile"; - } - - // Split filename between base and extension - // Add an extension if filename does not have one - int dotIndex = filename.indexOf('.'); - if (dotIndex < 0) { - if (mimeType != null) { - extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType); - if (extension != null) { - extension = "." + extension; - } - } - if (extension == null) { - if (mimeType != null && mimeType.toLowerCase().startsWith("text/")) { - if (mimeType.equalsIgnoreCase("text/html")) { - extension = ".html"; - } else { - extension = ".txt"; - } - } else { - extension = ".bin"; - } - } - } else { - if (mimeType != null) { - // Compare the last segment of the extension against the mime type. - // If there's a mismatch, discard the entire extension. - int lastDotIndex = filename.lastIndexOf('.'); - String typeFromExt = MimeTypeMap.getSingleton().getMimeTypeFromExtension( - filename.substring(lastDotIndex + 1)); - if (typeFromExt != null && !typeFromExt.equalsIgnoreCase(mimeType)) { - extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType); - if (extension != null) { - extension = "." + extension; - } - } - } - if (extension == null) { - extension = filename.substring(dotIndex); - } - filename = filename.substring(0, dotIndex); - } - - return filename + extension; - } - - /** Regex used to parse content-disposition headers */ - private static final Pattern CONTENT_DISPOSITION_PATTERN = - Pattern.compile("attachment;\\s*filename\\s*=\\s*\"([^\"]*)\""); - - /* - * Parse the Content-Disposition HTTP Header. The format of the header - * is defined here: http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html - * This header provides a filename for content that is going to be - * downloaded to the file system. We only support the attachment type. - */ - private static String parseContentDisposition(String contentDisposition) { - try { - Matcher m = CONTENT_DISPOSITION_PATTERN.matcher(contentDisposition); - if (m.find()) { - return m.group(1); - } - } catch (IllegalStateException ex) { - // This function is defined as returning null when it can't parse the header - } - return null; - } -} diff --git a/core/java/android/webkit/UrlInterceptHandler.java b/core/java/android/webkit/UrlInterceptHandler.java deleted file mode 100644 index e1c9d61..0000000 --- a/core/java/android/webkit/UrlInterceptHandler.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.webkit; - -import android.webkit.CacheManager.CacheResult; -import java.util.Map; - -public interface UrlInterceptHandler { - - /** - * Given an URL, returns the CacheResult which contains the - * surrogate response for the request, or null if the handler is - * not interested. - * - * @param url URL string. - * @param headers The headers associated with the request. May be null. - * @return The CacheResult containing the surrogate response. - */ - public CacheResult service(String url, Map<String, String> headers); -} diff --git a/core/java/android/webkit/UrlInterceptRegistry.java b/core/java/android/webkit/UrlInterceptRegistry.java deleted file mode 100644 index a218191..0000000 --- a/core/java/android/webkit/UrlInterceptRegistry.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.webkit; - -import android.webkit.CacheManager.CacheResult; -import android.webkit.UrlInterceptHandler; - -import java.util.Iterator; -import java.util.LinkedList; -import java.util.Map; - -public final class UrlInterceptRegistry { - - private final static String LOGTAG = "intercept"; - - private static boolean mDisabled = false; - - private static LinkedList mHandlerList; - - private static synchronized LinkedList getHandlers() { - if(mHandlerList == null) - mHandlerList = new LinkedList<UrlInterceptHandler>(); - return mHandlerList; - } - - /** - * set the flag to control whether url intercept is enabled or disabled - * - * @param disabled true to disable the cache - */ - public static synchronized void setUrlInterceptDisabled(boolean disabled) { - mDisabled = disabled; - } - - /** - * get the state of the url intercept, enabled or disabled - * - * @return return if it is disabled - */ - public static synchronized boolean urlInterceptDisabled() { - return mDisabled; - } - - /** - * Register a new UrlInterceptHandler. This handler will be called - * before any that were previously registered. - * - * @param handler The new UrlInterceptHandler object - * @return true if the handler was not previously registered. - */ - public static synchronized boolean registerHandler( - UrlInterceptHandler handler) { - if (!getHandlers().contains(handler)) { - getHandlers().addFirst(handler); - return true; - } else { - return false; - } - } - - /** - * Unregister a previously registered UrlInterceptHandler. - * - * @param handler A previously registered UrlInterceptHandler. - * @return true if the handler was found and removed from the list. - */ - public static synchronized boolean unregisterHandler( - UrlInterceptHandler handler) { - return getHandlers().remove(handler); - } - - /** - * Given an url, returns the CacheResult of the first - * UrlInterceptHandler interested, or null if none are. - * - * @return A CacheResult containing surrogate content. - */ - public static synchronized CacheResult getSurrogate( - String url, Map<String, String> headers) { - if (urlInterceptDisabled()) - return null; - Iterator iter = getHandlers().listIterator(); - while (iter.hasNext()) { - UrlInterceptHandler handler = (UrlInterceptHandler) iter.next(); - CacheResult result = handler.service(url, headers); - if (result != null) { - return result; - } - } - return null; - } -} diff --git a/core/java/android/webkit/WebBackForwardList.java b/core/java/android/webkit/WebBackForwardList.java deleted file mode 100644 index 9dea5ec..0000000 --- a/core/java/android/webkit/WebBackForwardList.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright (C) 2006 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.webkit; - -import android.util.Config; -import java.io.Serializable; -import java.util.ArrayList; - -/** - * This class contains the back/forward list for a WebView. - * WebView.copyBackForwardList() will return a copy of this class used to - * inspect the entries in the list. - */ -public class WebBackForwardList implements Cloneable, Serializable { - // Current position in the list. - private int mCurrentIndex; - // ArrayList of WebHistoryItems for maintaining our copy. - private ArrayList<WebHistoryItem> mArray; - // Flag to indicate that the list is invalid - private boolean mClearPending; - - /** - * Construct a back/forward list used by clients of WebView. - */ - /*package*/ WebBackForwardList() { - mCurrentIndex = -1; - mArray = new ArrayList<WebHistoryItem>(); - } - - /** - * Return the current history item. This method returns null if the list is - * empty. - * @return The current history item. - */ - public synchronized WebHistoryItem getCurrentItem() { - return getItemAtIndex(mCurrentIndex); - } - - /** - * Get the index of the current history item. This index can be used to - * directly index into the array list. - * @return The current index from 0...n or -1 if the list is empty. - */ - public synchronized int getCurrentIndex() { - return mCurrentIndex; - } - - /** - * Get the history item at the given index. The index range is from 0...n - * where 0 is the first item and n is the last item. - * @param index The index to retrieve. - */ - public synchronized WebHistoryItem getItemAtIndex(int index) { - if (index < 0 || index >= getSize()) { - return null; - } - return mArray.get(index); - } - - /** - * Get the total size of the back/forward list. - * @return The size of the list. - */ - public synchronized int getSize() { - return mArray.size(); - } - - /** - * Mark the back/forward list as having a pending clear. This is used on the - * UI side to mark the list as being invalid during the clearHistory method. - */ - /*package*/ synchronized void setClearPending() { - mClearPending = true; - } - - /** - * Return the status of the clear flag. This is used on the UI side to - * determine if the list is valid for checking things like canGoBack. - */ - /*package*/ synchronized boolean getClearPending() { - return mClearPending; - } - - /** - * Add a new history item to the list. This will remove all items after the - * current item and append the new item to the end of the list. Called from - * the WebCore thread only. Synchronized because the UI thread may be - * reading the array or the current index. - * @param item A new history item. - */ - /*package*/ synchronized void addHistoryItem(WebHistoryItem item) { - // Update the current position because we are going to add the new item - // in that slot. - ++mCurrentIndex; - // If the current position is not at the end, remove all history items - // after the current item. - final int size = mArray.size(); - final int newPos = mCurrentIndex; - if (newPos != size) { - for (int i = size - 1; i >= newPos; i--) { - final WebHistoryItem h = mArray.remove(i); - } - } - // Add the item to the list. - mArray.add(item); - } - - /** - * Clear the back/forward list. Called from the WebCore thread. - */ - /*package*/ synchronized void close(int nativeFrame) { - // Clear the array first because nativeClose will call addHistoryItem - // with the current item. - mArray.clear(); - mCurrentIndex = -1; - nativeClose(nativeFrame); - // Reset the clear flag - mClearPending = false; - } - - /* Remove the item at the given index. Called by JNI only. */ - 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 - // native BackForwardList. - if (Config.DEBUG && (index != 0)) { - throw new AssertionError(); - } - final WebHistoryItem h = mArray.remove(index); - // XXX: If we ever add another callback for removing history items at - // any index, this will no longer be valid. - mCurrentIndex--; - } - - /** - * Clone the entire object to be used in the UI thread by clients of - * WebView. This creates a copy that should never be modified by any of the - * webkit package classes. - */ - protected synchronized WebBackForwardList clone() { - WebBackForwardList l = new WebBackForwardList(); - if (mClearPending) { - // If a clear is pending, return a copy with only the current item. - l.addHistoryItem(getCurrentItem()); - return l; - } - l.mCurrentIndex = mCurrentIndex; - int size = getSize(); - l.mArray = new ArrayList<WebHistoryItem>(size); - for (int i = 0; i < size; i++) { - // Add a copy of each WebHistoryItem - l.mArray.add(mArray.get(i).clone()); - } - return l; - } - - /** - * Set the new history index. - * @param newIndex The new history index. - */ - /*package*/ synchronized void setCurrentIndex(int newIndex) { - mCurrentIndex = newIndex; - } - - /** - * Restore the history index. - */ - /*package*/ static native synchronized void restoreIndex(int nativeFrame, - int index); - - /* Close the native list. */ - private static native void nativeClose(int nativeFrame); -} diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java deleted file mode 100644 index f940006..0000000 --- a/core/java/android/webkit/WebChromeClient.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.webkit; - -import android.graphics.Bitmap; -import android.os.Message; - -public class WebChromeClient { - - /** - * Tell the host application the current progress of loading a page. - * @param view The WebView that initiated the callback. - * @param newProgress Current page loading progress, represented by - * an integer between 0 and 100. - */ - public void onProgressChanged(WebView view, int newProgress) {} - - /** - * Notify the host application of a change in the document title. - * @param view The WebView that initiated the callback. - * @param title A String containing the new title of the document. - */ - public void onReceivedTitle(WebView view, String title) {} - - /** - * Notify the host application of a new favicon for the current page. - * @param view The WebView that initiated the callback. - * @param icon A Bitmap containing the favicon for the current page. - */ - public void onReceivedIcon(WebView view, Bitmap icon) {} - - /** - * Request the host application to create a new Webview. The host - * application should handle placement of the new WebView in the view - * system. The default behavior returns null. - * @param view The WebView that initiated the callback. - * @param dialog True if the new window is meant to be a small dialog - * window. - * @param userGesture True if the request was initiated by a user gesture - * such as clicking a link. - * @param resultMsg The message to send when done creating a new WebView. - * Set the new WebView through resultMsg.obj which is - * WebView.WebViewTransport() and then call - * resultMsg.sendToTarget(); - * @return Similar to javscript dialogs, this method should return true if - * the client is going to handle creating a new WebView. Note that - * the WebView will halt processing if this method returns true so - * make sure to call resultMsg.sendToTarget(). It is undefined - * behavior to call resultMsg.sendToTarget() after returning false - * from this method. - */ - public boolean onCreateWindow(WebView view, boolean dialog, - boolean userGesture, Message resultMsg) { - return false; - } - - /** - * Request display and focus for this WebView. This may happen due to - * another WebView opening a link in this WebView and requesting that this - * WebView be displayed. - * @param view The WebView that needs to be focused. - */ - public void onRequestFocus(WebView view) {} - - /** - * Notify the host application to close the given WebView and remove it - * from the view system if necessary. At this point, WebCore has stopped - * any loading in this window and has removed any cross-scripting ability - * in javascript. - * @param window The WebView that needs to be closed. - */ - public void onCloseWindow(WebView window) {} - - /** - * Tell the client to display a javascript alert dialog. If the client - * returns true, WebView will assume that the client will handle the - * dialog. If the client returns false, it will continue execution. - * @param view The WebView that initiated the callback. - * @param url The url of the page requesting the dialog. - * @param message Message to be displayed in the window. - * @param result A JsResult to confirm that the user hit enter. - * @return boolean Whether the client will handle the alert dialog. - */ - public boolean onJsAlert(WebView view, String url, String message, - JsResult result) { - return false; - } - - /** - * Tell the client to display a confirm dialog to the user. If the client - * returns true, WebView will assume that the client will handle the - * confirm dialog and call the appropriate JsResult method. If the - * client returns false, a default value of false will be returned to - * javascript. The default behavior is to return false. - * @param view The WebView that initiated the callback. - * @param url The url of the page requesting the dialog. - * @param message Message to be displayed in the window. - * @param result A JsResult used to send the user's response to - * javascript. - * @return boolean Whether the client will handle the confirm dialog. - */ - public boolean onJsConfirm(WebView view, String url, String message, - JsResult result) { - return false; - } - - /** - * Tell the client to display a prompt dialog to the user. If the client - * returns true, WebView will assume that the client will handle the - * prompt dialog and call the appropriate JsPromptResult method. If the - * client returns false, a default value of false will be returned to to - * javascript. The default behavior is to return false. - * @param view The WebView that initiated the callback. - * @param url The url of the page requesting the dialog. - * @param message Message to be displayed in the window. - * @param defaultValue The default value displayed in the prompt dialog. - * @param result A JsPromptResult used to send the user's reponse to - * javascript. - * @return boolean Whether the client will handle the prompt dialog. - */ - public boolean onJsPrompt(WebView view, String url, String message, - String defaultValue, JsPromptResult result) { - return false; - } - - /** - * Tell the client to display a dialog to confirm navigation away from the - * current page. This is the result of the onbeforeunload javascript event. - * If the client returns true, WebView will assume that the client will - * handle the confirm dialog and call the appropriate JsResult method. If - * the client returns false, a default value of true will be returned to - * javascript to accept navigation away from the current page. The default - * behavior is to return false. Setting the JsResult to true will navigate - * away from the current page, false will cancel the navigation. - * @param view The WebView that initiated the callback. - * @param url The url of the page requesting the dialog. - * @param message Message to be displayed in the window. - * @param result A JsResult used to send the user's response to - * javascript. - * @return boolean Whether the client will handle the confirm dialog. - */ - public boolean onJsBeforeUnload(WebView view, String url, String message, - JsResult result) { - return false; - } -} diff --git a/core/java/android/webkit/WebHistoryItem.java b/core/java/android/webkit/WebHistoryItem.java deleted file mode 100644 index a408e06..0000000 --- a/core/java/android/webkit/WebHistoryItem.java +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright (C) 2006 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.webkit; - -import android.graphics.Bitmap; - -/** - * A convenience class for accessing fields in an entry in the back/forward list - * of a WebView. Each WebHistoryItem is a snapshot of the requested history - * item. Each history item may be updated during the load of a page. - * @see WebBackForwardList - */ -public class WebHistoryItem implements Cloneable { - // Global identifier count. - private static int sNextId = 0; - // Unique identifier. - private final int mId; - // The title of this item's document. - 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. - private byte[] mFlattenedData; - - /** - * Basic constructor that assigns a unique id to the item. Called by JNI - * only. - */ - private WebHistoryItem() { - synchronized (WebHistoryItem.class) { - mId = sNextId++; - } - } - - /** - * Construct a new WebHistoryItem with initial flattened data. - * @param data The pre-flattened data coming from restoreState. - */ - /*package*/ WebHistoryItem(byte[] data) { - mUrl = null; // This will be updated natively - mFlattenedData = data; - synchronized (WebHistoryItem.class) { - mId = sNextId++; - } - } - - /** - * Construct a clone of a WebHistoryItem from the given item. - * @param item The history item to clone. - */ - private WebHistoryItem(WebHistoryItem item) { - mUrl = item.mUrl; - mTitle = item.mTitle; - mFlattenedData = item.mFlattenedData; - mFavicon = item.mFavicon; - mId = item.mId; -} - - /** - * Return an identifier for this history item. If an item is a copy of - * another item, the identifiers will be the same even if they are not the - * same object. - * @return The id for this item. - */ - public int getId() { - return mId; - } - - /** - * Return the url of this history item. The url is the base url of this - * history item. See getTargetUrl() for the url that is the actual target of - * this history item. - * @return The base url of this history item. - * Note: The VM ensures 32-bit atomic read/write operations so we don't have - * to synchronize this method. - */ - public String getUrl() { - return mUrl; - } - - /** - * 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 - * to synchronize this method. - */ - public String getTitle() { - return mTitle; - } - - /** - * Return the favicon of this history item or null if no favicon was found. - * @return A Bitmap containing the favicon for this history item or null. - * Note: The VM ensures 32-bit atomic read/write operations so we don't have - * to synchronize this method. - */ - public Bitmap getFavicon() { - return mFavicon; - } - - /** - * Set the favicon. - * @param icon A Bitmap containing the favicon for this history item. - * Note: The VM ensures 32-bit atomic read/write operations so we don't have - * to synchronize this method. - */ - /*package*/ void setFavicon(Bitmap icon) { - mFavicon = icon; - } - - /** - * Get the pre-flattened data. - * Note: The VM ensures 32-bit atomic read/write operations so we don't have - * to synchronize this method. - */ - /*package*/ byte[] getFlattenedData() { - return mFlattenedData; - } - - /** - * Inflate this item. - * Note: The VM ensures 32-bit atomic read/write operations so we don't have - * to synchronize this method. - */ - /*package*/ void inflate(int nativeFrame) { - inflate(nativeFrame, mFlattenedData); - } - - /** - * Clone the history item for use by clients of WebView. - */ - protected synchronized WebHistoryItem clone() { - return new WebHistoryItem(this); - } - - /* Natively inflate this item, this method is called in the WebCore thread. - */ - private native void inflate(int nativeFrame, byte[] data); - - /* Called by jni when the item is updated */ - 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/WebIconDatabase.java b/core/java/android/webkit/WebIconDatabase.java deleted file mode 100644 index d284f5e..0000000 --- a/core/java/android/webkit/WebIconDatabase.java +++ /dev/null @@ -1,251 +0,0 @@ -/* - * Copyright (C) 2006 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.webkit; - -import android.os.Handler; -import android.os.Message; -import android.graphics.Bitmap; - -import java.util.Vector; - -/** - * Functions for manipulating the icon database used by WebView. - * These functions require that a WebView be constructed before being invoked - * and WebView.getIconDatabase() will return a WebIconDatabase object. This - * WebIconDatabase object is a single instance and all methods operate on that - * single object. - */ -public final class WebIconDatabase { - // Global instance of a WebIconDatabase - private static WebIconDatabase sIconDatabase; - // EventHandler for handling messages before and after the WebCore thread is - // ready. - private final EventHandler mEventHandler = new EventHandler(); - - // Class to handle messages before WebCore is ready - private class EventHandler extends Handler { - // Message ids - static final int OPEN = 0; - static final int CLOSE = 1; - static final int REMOVE_ALL = 2; - static final int REQUEST_ICON = 3; - static final int RETAIN_ICON = 4; - static final int RELEASE_ICON = 5; - // Message for dispatching icon request results - private static final int ICON_RESULT = 10; - // Actual handler that runs in WebCore thread - private Handler mHandler; - // Vector of messages before the WebCore thread is ready - private Vector<Message> mMessages = new Vector<Message>(); - // Class to handle a result dispatch - private class IconResult { - private final String mUrl; - private final Bitmap mIcon; - private final IconListener mListener; - IconResult(String url, Bitmap icon, IconListener l) { - mUrl = url; - mIcon = icon; - mListener = l; - } - void dispatch() { - mListener.onReceivedIcon(mUrl, mIcon); - } - } - - @Override - public void handleMessage(Message msg) { - // Note: This is the message handler for the UI thread. - switch (msg.what) { - case ICON_RESULT: - ((IconResult) msg.obj).dispatch(); - break; - } - } - - // Called by WebCore thread to create the actual handler - private synchronized void createHandler() { - if (mHandler == null) { - mHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - // Note: This is the message handler for the WebCore - // thread. - switch (msg.what) { - case OPEN: - nativeOpen((String) msg.obj); - break; - - case CLOSE: - nativeClose(); - break; - - case REMOVE_ALL: - nativeRemoveAllIcons(); - break; - - case REQUEST_ICON: - IconListener l = (IconListener) msg.obj; - String url = msg.getData().getString("url"); - Bitmap icon = nativeIconForPageUrl(url); - if (icon != null) { - EventHandler.this.sendMessage( - Message.obtain(null, ICON_RESULT, - new IconResult(url, icon, l))); - } - break; - - case RETAIN_ICON: - nativeRetainIconForPageUrl((String) msg.obj); - break; - - case RELEASE_ICON: - nativeReleaseIconForPageUrl((String) msg.obj); - break; - } - } - }; - // Transfer all pending messages - for (int size = mMessages.size(); size > 0; size--) { - mHandler.sendMessage(mMessages.remove(0)); - } - mMessages = null; - } - } - - private synchronized void postMessage(Message msg) { - if (mMessages != null) { - mMessages.add(msg); - } else { - mHandler.sendMessage(msg); - } - } - } - - /** - * Interface for receiving icons from the database. - */ - public interface IconListener { - /** - * Called when the icon has been retrieved from the database and the - * result is non-null. - * @param url The url passed in the request. - * @param icon The favicon for the given url. - */ - public void onReceivedIcon(String url, Bitmap icon); - } - - /** - * Open a the icon database and store the icons in the given path. - * @param path The directory path where the icon database will be stored. - * @return True if the database was successfully opened or created in - * the given path. - */ - public void open(String path) { - if (path != null) { - mEventHandler.postMessage( - Message.obtain(null, EventHandler.OPEN, path)); - } - } - - /** - * Close the shared instance of the icon database. - */ - public void close() { - mEventHandler.postMessage( - Message.obtain(null, EventHandler.CLOSE)); - } - - /** - * Removes all the icons in the database. - */ - public void removeAllIcons() { - mEventHandler.postMessage( - Message.obtain(null, EventHandler.REMOVE_ALL)); - } - - /** - * Request the Bitmap representing the icon for the given page - * url. If the icon exists, the listener will be called with the result. - * @param url The page's url. - * @param listener An implementation on IconListener to receive the result. - */ - public void requestIconForPageUrl(String url, IconListener listener) { - if (listener == null || url == null) { - return; - } - Message msg = Message.obtain(null, EventHandler.REQUEST_ICON, listener); - msg.getData().putString("url", url); - mEventHandler.postMessage(msg); - } - - /** - * Retain the icon for the given page url. - * @param url The page's url. - */ - public void retainIconForPageUrl(String url) { - if (url != null) { - mEventHandler.postMessage( - Message.obtain(null, EventHandler.RETAIN_ICON, url)); - } - } - - /** - * Release the icon for the given page url. - * @param url The page's url. - */ - public void releaseIconForPageUrl(String url) { - if (url != null) { - mEventHandler.postMessage( - Message.obtain(null, EventHandler.RELEASE_ICON, url)); - } - } - - /** - * Get the global instance of WebIconDatabase. - * @return A single instance of WebIconDatabase. It will be the same - * instance for the current process each time this method is - * called. - */ - public static WebIconDatabase getInstance() { - // XXX: Must be created in the UI thread. - if (sIconDatabase == null) { - sIconDatabase = new WebIconDatabase(); - } - return sIconDatabase; - } - - /** - * Create the internal handler and transfer all pending messages. - * XXX: Called by WebCore thread only! - */ - /*package*/ void createHandler() { - mEventHandler.createHandler(); - } - - /** - * Private constructor to avoid anyone else creating an instance. - */ - private WebIconDatabase() {} - - // Native functions - private static native void nativeOpen(String path); - private static native void nativeClose(); - private static native void nativeRemoveAllIcons(); - private static native Bitmap nativeIconForPageUrl(String url); - private static native void nativeRetainIconForPageUrl(String url); - private static native void nativeReleaseIconForPageUrl(String url); -} diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java deleted file mode 100644 index 4e2b2ab..0000000 --- a/core/java/android/webkit/WebSettings.java +++ /dev/null @@ -1,1112 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.webkit; - -import android.content.Context; -import android.os.Build; -import android.os.Handler; -import android.os.Message; -import android.provider.Checkin; - -import java.lang.SecurityException; -import android.content.pm.PackageManager; - -import java.util.Locale; - -/** - * Manages settings state for a WebView. When a WebView is first created, it - * obtains a set of default settings. These default settings will be returned - * from any getter call. A WebSettings object obtained from - * WebView.getSettings() is tied to the life of the WebView. If a WebView has - * been destroyed, any method call on WebSettings will throw an - * IllegalStateException. - */ -public class WebSettings { - /** - * Enum for controlling the layout of html. - * NORMAL means no rendering changes. - * SINGLE_COLUMN moves all content into one column that is the width of the - * view. - * NARROW_COLUMNS makes all columns no wider than the screen if possible. - */ - // XXX: These must match LayoutAlgorithm in Settings.h in WebCore. - public enum LayoutAlgorithm { - NORMAL, - SINGLE_COLUMN, - NARROW_COLUMNS - } - - /** - * Enum for specifying the text size. - * SMALLEST is 50% - * SMALLER is 75% - * NORMAL is 100% - * LARGER is 150% - * LARGEST is 200% - */ - public enum TextSize { - SMALLEST(50), - SMALLER(75), - NORMAL(100), - LARGER(150), - LARGEST(200); - TextSize(int size) { - value = size; - } - int value; - } - - /** - * Default cache usage pattern Use with {@link #setCacheMode}. - */ - public static final int LOAD_DEFAULT = -1; - - /** - * Normal cache usage pattern Use with {@link #setCacheMode}. - */ - public static final int LOAD_NORMAL = 0; - - /** - * Use cache if content is there, even if expired (eg, history nav) - * If it is not in the cache, load from network. - * Use with {@link #setCacheMode}. - */ - public static final int LOAD_CACHE_ELSE_NETWORK = 1; - - /** - * Don't use the cache, load from network - * Use with {@link #setCacheMode}. - */ - public static final int LOAD_NO_CACHE = 2; - - /** - * Don't use the network, load from cache only. - * Use with {@link #setCacheMode}. - */ - public static final int LOAD_CACHE_ONLY = 3; - - public enum RenderPriority { - NORMAL, - HIGH, - LOW - } - - // BrowserFrame used to access the native frame pointer. - private BrowserFrame mBrowserFrame; - // Flag to prevent multiple SYNC messages at one time. - private boolean mSyncPending = false; - // Custom handler that queues messages until the WebCore thread is active. - private final EventHandler mEventHandler; - // Private settings so we don't have to go into native code to - // 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"; - private String mSansSerifFontFamily = "sans-serif"; - private String mSerifFontFamily = "serif"; - private String mCursiveFontFamily = "cursive"; - private String mFantasyFontFamily = "fantasy"; - private String mDefaultTextEncoding = "Latin-1"; - private String mUserAgent; - private boolean mUseDefaultUserAgent; - private String mAcceptLanguage; - private String mPluginsPath = ""; - private int mMinimumFontSize = 8; - private int mMinimumLogicalFontSize = 8; - private int mDefaultFontSize = 16; - 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. - private RenderPriority mRenderPriority = RenderPriority.NORMAL; - private int mOverrideCacheMode = LOAD_DEFAULT; - private boolean mSaveFormData = true; - private boolean mSavePassword = true; - private boolean mLightTouchEnabled = false; - 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 { - // Message id for syncing - static final int SYNC = 0; - // Message id for setting priority - static final int PRIORITY = 1; - // Actual WebCore thread handler - private Handler mHandler; - - private synchronized void createHandler() { - // as mRenderPriority can be set before thread is running, sync up - setRenderPriority(); - - // create a new handler - mHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case SYNC: - synchronized (WebSettings.this) { - if (mBrowserFrame.mNativeFrame != 0) { - nativeSync(mBrowserFrame.mNativeFrame); - } - mSyncPending = false; - } - break; - - case PRIORITY: { - setRenderPriority(); - break; - } - } - } - }; - } - - private void setRenderPriority() { - synchronized (WebSettings.this) { - if (mRenderPriority == RenderPriority.NORMAL) { - android.os.Process.setThreadPriority( - android.os.Process.THREAD_PRIORITY_DEFAULT); - } else if (mRenderPriority == RenderPriority.HIGH) { - android.os.Process.setThreadPriority( - android.os.Process.THREAD_PRIORITY_FOREGROUND + - android.os.Process.THREAD_PRIORITY_LESS_FAVORABLE); - } else if (mRenderPriority == RenderPriority.LOW) { - android.os.Process.setThreadPriority( - android.os.Process.THREAD_PRIORITY_BACKGROUND); - } - } - } - - /** - * Send a message to the private queue or handler. - */ - private synchronized boolean sendMessage(Message msg) { - if (mHandler != null) { - mHandler.sendMessage(msg); - return true; - } else { - return false; - } - } - } - - // User agent strings. - private static final String DESKTOP_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) { - 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); - } - } - 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) { - buffer.append("-"); - buffer.append(country); - } - } - } - - 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()); - } - } else { - // default to "en" - buffer.append("en"); - } - - final String model = Build.MODEL; - if (model.length() > 0) { - buffer.append("; "); - buffer.append(model); - } - final String id = Build.ID; - if (id.length() > 0) { - buffer.append(" Build/"); - buffer.append(id); - } - 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. - */ - public void setNavDump(boolean enabled) { - mNavDump = enabled; - } - - /** - * Returns true if dumping the navigation cache is enabled. - */ - public boolean getNavDump() { - return mNavDump; - } - - /** - * Set whether the WebView supports zoom - */ - public void setSupportZoom(boolean support) { - mSupportZoom = support; - } - - /** - * Returns whether the WebView supports zoom - */ - public boolean supportZoom() { - return mSupportZoom; - } - - /** - * 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) { - mSaveFormData = save; - } - - /** - * Return whether the WebView is saving form data. - */ - public boolean getSaveFormData() { - return mSaveFormData; - } - - /** - * Store whether the WebView is saving password. - */ - public void setSavePassword(boolean save) { - mSavePassword = save; - } - - /** - * Return whether the WebView is saving password. - */ - public boolean getSavePassword() { - return mSavePassword; - } - - /** - * Set the text size of the page. - * @param t A TextSize value for increasing or decreasing the text. - * @see WebSettings.TextSize - */ - public synchronized void setTextSize(TextSize t) { - if (WebView.mLogEvent && mTextSize != t ) { - Checkin.updateStats(mContext.getContentResolver(), - Checkin.Stats.Tag.BROWSER_TEXT_SIZE_CHANGE, 1, 0.0); - } - mTextSize = t; - postSync(); - } - - /** - * Get the text size of the page. - * @return A TextSize enum value describing the text size. - * @see WebSettings.TextSize - */ - public synchronized TextSize getTextSize() { - return mTextSize; - } - - /** - * Enables using light touches to make a selection and activate mouseovers. - */ - public void setLightTouchEnabled(boolean enabled) { - mLightTouchEnabled = enabled; - } - - /** - * Returns true if light touches are enabled. - */ - public boolean getLightTouchEnabled() { - return mLightTouchEnabled; - } - - /** - * Tell the WebView to use the double tree rendering algorithm. - * @param use True if the WebView is to use double tree rendering, false - * otherwise. - */ - public synchronized void setUseDoubleTree(boolean use) { - if (mUseDoubleTree != use) { - mUseDoubleTree = use; - postSync(); - } - } - - /** - * Return true if the WebView is using the double tree rendering algorithm. - * @return True if the WebView is using the double tree rendering - * algorithm. - */ - public synchronized boolean getUseDoubleTree() { - return mUseDoubleTree; - } - - /** - * 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. - * - * @deprecated Please use setUserAgentString instead. - */ - @Deprecated - public synchronized void setUserAgent(int ua) { - 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. - * -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 -1; - } - - /** - * Tell the WebView to use the wide viewport - */ - public synchronized void setUseWideViewPort(boolean use) { - if (mUseWideViewport != use) { - mUseWideViewport = use; - postSync(); - } - } - - /** - * @return True if the WebView is using a wide viewport - */ - public synchronized boolean getUseWideViewPort() { - return mUseWideViewport; - } - - /** - * Tell the WebView whether it supports multiple windows. TRUE means - * that {@link WebChromeClient#onCreateWindow(WebView, boolean, - * boolean, Message)} is implemented by the host application. - */ - public synchronized void setSupportMultipleWindows(boolean support) { - if (mSupportMultipleWindows != support) { - mSupportMultipleWindows = support; - postSync(); - } - } - - /** - * @return True if the WebView is supporting multiple windows. This means - * that {@link WebChromeClient#onCreateWindow(WebView, boolean, - * boolean, Message)} is implemented by the host application. - */ - public synchronized boolean supportMultipleWindows() { - return mSupportMultipleWindows; - } - - /** - * Set the underlying layout algorithm. This will cause a relayout of the - * WebView. - * @param l A LayoutAlgorithm enum specifying the algorithm to use. - * @see WebSettings.LayoutAlgorithm - */ - public synchronized void setLayoutAlgorithm(LayoutAlgorithm l) { - // XXX: This will only be affective if libwebcore was built with - // ANDROID_LAYOUT defined. - if (mLayoutAlgorithm != l) { - mLayoutAlgorithm = l; - postSync(); - } - } - - /** - * Return the current layout algorithm. - * @return LayoutAlgorithm enum value describing the layout algorithm - * being used. - * @see WebSettings.LayoutAlgorithm - */ - public synchronized LayoutAlgorithm getLayoutAlgorithm() { - return mLayoutAlgorithm; - } - - /** - * Set the standard font family name. - * @param font A font family name. - */ - public synchronized void setStandardFontFamily(String font) { - if (font != null && !font.equals(mStandardFontFamily)) { - mStandardFontFamily = font; - postSync(); - } - } - - /** - * Get the standard font family name. - * @return The standard font family name as a string. - */ - public synchronized String getStandardFontFamily() { - return mStandardFontFamily; - } - - /** - * Set the fixed font family name. - * @param font A font family name. - */ - public synchronized void setFixedFontFamily(String font) { - if (font != null && !font.equals(mFixedFontFamily)) { - mFixedFontFamily = font; - postSync(); - } - } - - /** - * Get the fixed font family name. - * @return The fixed font family name as a string. - */ - public synchronized String getFixedFontFamily() { - return mFixedFontFamily; - } - - /** - * Set the sans-serif font family name. - * @param font A font family name. - */ - public synchronized void setSansSerifFontFamily(String font) { - if (font != null && !font.equals(mSansSerifFontFamily)) { - mSansSerifFontFamily = font; - postSync(); - } - } - - /** - * Get the sans-serif font family name. - * @return The sans-serif font family name as a string. - */ - public synchronized String getSansSerifFontFamily() { - return mSansSerifFontFamily; - } - - /** - * Set the serif font family name. - * @param font A font family name. - */ - public synchronized void setSerifFontFamily(String font) { - if (font != null && !font.equals(mSerifFontFamily)) { - mSerifFontFamily = font; - postSync(); - } - } - - /** - * Get the serif font family name. - * @return The serif font family name as a string. - */ - public synchronized String getSerifFontFamily() { - return mSerifFontFamily; - } - - /** - * Set the cursive font family name. - * @param font A font family name. - */ - public synchronized void setCursiveFontFamily(String font) { - if (font != null && !font.equals(mCursiveFontFamily)) { - mCursiveFontFamily = font; - postSync(); - } - } - - /** - * Get the cursive font family name. - * @return The cursive font family name as a string. - */ - public synchronized String getCursiveFontFamily() { - return mCursiveFontFamily; - } - - /** - * Set the fantasy font family name. - * @param font A font family name. - */ - public synchronized void setFantasyFontFamily(String font) { - if (font != null && !font.equals(mFantasyFontFamily)) { - mFantasyFontFamily = font; - postSync(); - } - } - - /** - * Get the fantasy font family name. - * @return The fantasy font family name as a string. - */ - public synchronized String getFantasyFontFamily() { - return mFantasyFontFamily; - } - - /** - * Set the minimum font size. - * @param size A non-negative integer between 1 and 72. - * Any number outside the specified range will be pinned. - */ - public synchronized void setMinimumFontSize(int size) { - size = pin(size); - if (mMinimumFontSize != size) { - mMinimumFontSize = size; - postSync(); - } - } - - /** - * Get the minimum font size. - * @return A non-negative integer between 1 and 72. - */ - public synchronized int getMinimumFontSize() { - return mMinimumFontSize; - } - - /** - * Set the minimum logical font size. - * @param size A non-negative integer between 1 and 72. - * Any number outside the specified range will be pinned. - */ - public synchronized void setMinimumLogicalFontSize(int size) { - size = pin(size); - if (mMinimumLogicalFontSize != size) { - mMinimumLogicalFontSize = size; - postSync(); - } - } - - /** - * Get the minimum logical font size. - * @return A non-negative integer between 1 and 72. - */ - public synchronized int getMinimumLogicalFontSize() { - return mMinimumLogicalFontSize; - } - - /** - * Set the default font size. - * @param size A non-negative integer between 1 and 72. - * Any number outside the specified range will be pinned. - */ - public synchronized void setDefaultFontSize(int size) { - size = pin(size); - if (mDefaultFontSize != size) { - mDefaultFontSize = size; - postSync(); - } - } - - /** - * Get the default font size. - * @return A non-negative integer between 1 and 72. - */ - public synchronized int getDefaultFontSize() { - return mDefaultFontSize; - } - - /** - * Set the default fixed font size. - * @param size A non-negative integer between 1 and 72. - * Any number outside the specified range will be pinned. - */ - public synchronized void setDefaultFixedFontSize(int size) { - size = pin(size); - if (mDefaultFixedFontSize != size) { - mDefaultFixedFontSize = size; - postSync(); - } - } - - /** - * Get the default fixed font size. - * @return A non-negative integer between 1 and 72. - */ - public synchronized int getDefaultFixedFontSize() { - return mDefaultFixedFontSize; - } - - /** - * Tell the WebView to load image resources automatically. - * @param flag True if the WebView should load images automatically. - */ - public synchronized void setLoadsImagesAutomatically(boolean flag) { - if (mLoadsImagesAutomatically != flag) { - mLoadsImagesAutomatically = flag; - postSync(); - } - } - - /** - * Return true if the WebView will load image resources automatically. - * @return True if the WebView loads images automatically. - */ - public synchronized boolean getLoadsImagesAutomatically() { - return mLoadsImagesAutomatically; - } - - /** - * Tell the WebView to block network image. This is only checked when - * getLoadsImagesAutomatically() is true. - * @param flag True if the WebView should block network image - */ - public synchronized void setBlockNetworkImage(boolean flag) { - if (mBlockNetworkImage != flag) { - mBlockNetworkImage = flag; - postSync(); - } - } - - /** - * Return true if the WebView will block network image. - * @return True if the WebView blocks network image. - */ - 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. - * @param flag True if the WebView should execute javascript. - */ - public synchronized void setJavaScriptEnabled(boolean flag) { - if (mJavaScriptEnabled != flag) { - mJavaScriptEnabled = flag; - postSync(); - } - } - - /** - * Tell the WebView to enable plugins. - * @param flag True if the WebView should load plugins. - */ - public synchronized void setPluginsEnabled(boolean flag) { - if (mPluginsEnabled != flag) { - mPluginsEnabled = flag; - postSync(); - } - } - - /** - * Set a custom path to plugins used by the WebView. The client - * must ensure it exists before this call. - * @param pluginsPath String path to the directory containing plugins. - */ - public synchronized void setPluginsPath(String pluginsPath) { - if (pluginsPath != null && !pluginsPath.equals(mPluginsPath)) { - mPluginsPath = pluginsPath; - postSync(); - } - } - - /** - * Return true if javascript is enabled. - * @return True if javascript is enabled. - */ - public synchronized boolean getJavaScriptEnabled() { - return mJavaScriptEnabled; - } - - /** - * Return true if plugins are enabled. - * @return True if plugins are enabled. - */ - public synchronized boolean getPluginsEnabled() { - return mPluginsEnabled; - } - - /** - * Return the current path used for plugins in the WebView. - * @return The string path to the WebView plugins. - */ - public synchronized String getPluginsPath() { - return mPluginsPath; - } - - /** - * Tell javascript to open windows automatically. This applies to the - * javascript function window.open(). - * @param flag True if javascript can open windows automatically. - */ - public synchronized void setJavaScriptCanOpenWindowsAutomatically( - boolean flag) { - if (mJavaScriptCanOpenWindowsAutomatically != flag) { - mJavaScriptCanOpenWindowsAutomatically = flag; - postSync(); - } - } - - /** - * Return true if javascript can open windows automatically. - * @return True if javascript can open windows automatically during - * window.open(). - */ - public synchronized boolean getJavaScriptCanOpenWindowsAutomatically() { - return mJavaScriptCanOpenWindowsAutomatically; - } - - /** - * Set the default text encoding name to use when decoding html pages. - * @param encoding The text encoding name. - */ - public synchronized void setDefaultTextEncodingName(String encoding) { - if (encoding != null && !encoding.equals(mDefaultTextEncoding)) { - mDefaultTextEncoding = encoding; - postSync(); - } - } - - /** - * Get the default text encoding name. - * @return The default text encoding name as a string. - */ - public synchronized String getDefaultTextEncodingName() { - return mDefaultTextEncoding; - } - - /** - * 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. - * - * @param flag - */ - public void setNeedInitialFocus(boolean flag) { - if (mNeedInitialFocus != flag) { - mNeedInitialFocus = flag; - } - } - - /* Package api to get the choice whether it needs to set initial focus. */ - /* package */ boolean getNeedInitialFocus() { - return mNeedInitialFocus; - } - - /** - * Set the priority of the Render thread. Unlike the other settings, this - * one only needs to be called once per process. - * - * @param priority RenderPriority, can be normal, high or low. - */ - public synchronized void setRenderPriority(RenderPriority priority) { - if (mRenderPriority != priority) { - mRenderPriority = priority; - mEventHandler.sendMessage(Message.obtain(null, - EventHandler.PRIORITY)); - } - } - - /** - * Override the way the cache is used. The way the cache is used is based - * on the navigation option. For a normal page load, the cache is checked - * and content is re-validated as needed. When navigating back, content is - * not revalidated, instead the content is just pulled from the cache. - * This function allows the client to override this behavior. - * @param mode One of the LOAD_ values. - */ - public void setCacheMode(int mode) { - if (mode != mOverrideCacheMode) { - mOverrideCacheMode = mode; - } - } - - /** - * Return the current setting for overriding the cache mode. For a full - * description, see the {@link #setCacheMode(int)} function. - */ - 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 - * WebCore thread. - */ - /*package*/ - synchronized void syncSettingsAndCreateHandler(BrowserFrame frame) { - mBrowserFrame = frame; - if (android.util.Config.DEBUG) { - junit.framework.Assert.assertTrue(frame.mNativeFrame != 0); - } - nativeSync(frame.mNativeFrame); - mSyncPending = false; - mEventHandler.createHandler(); - } - - private int pin(int size) { - // FIXME: 72 is just an arbitrary max text size value. - if (size < 1) { - return 1; - } else if (size > 72) { - return 72; - } - return size; - } - - /* Post a SYNC message to handle syncing the native settings. */ - private synchronized void postSync() { - // Only post if a sync is not pending - if (!mSyncPending) { - mSyncPending = mEventHandler.sendMessage( - Message.obtain(null, EventHandler.SYNC)); - } - } - - // Synchronize the native and java settings. - private native void nativeSync(int nativeFrame); -} diff --git a/core/java/android/webkit/WebSyncManager.java b/core/java/android/webkit/WebSyncManager.java deleted file mode 100644 index e6e9994..0000000 --- a/core/java/android/webkit/WebSyncManager.java +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.webkit; - -import android.content.Context; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.os.Process; -import android.util.Config; -import android.util.Log; - -abstract class WebSyncManager implements Runnable { - // message code for sync message - private static final int SYNC_MESSAGE = 101; - // time delay in millisec for a sync (now) message - private static int SYNC_NOW_INTERVAL = 100; // 100 millisec - // time delay in millisec for a sync (later) message - private static int SYNC_LATER_INTERVAL = 5 * 60 * 1000; // 5 minutes - // thread for syncing - private Thread mSyncThread; - // Name of thread - private String mThreadName; - // handler of the sync thread - protected Handler mHandler; - // database for the persistent storage - protected WebViewDatabase mDataBase; - // Ref count for calls to start/stop sync - private int mStartSyncRefCount; - // log tag - protected static final String LOGTAG = "websync"; - - private class SyncHandler extends Handler { - @Override - public void handleMessage(Message msg) { - if (msg.what == SYNC_MESSAGE) { - if (Config.LOGV) { - Log.v(LOGTAG, "*** WebSyncManager sync ***"); - } - syncFromRamToFlash(); - - // send a delayed message to request sync later - Message newmsg = obtainMessage(SYNC_MESSAGE); - sendMessageDelayed(newmsg, SYNC_LATER_INTERVAL); - } - } - } - - protected WebSyncManager(Context context, String name) { - mThreadName = name; - if (context != null) { - mDataBase = WebViewDatabase.getInstance(context); - mSyncThread = new Thread(this); - mSyncThread.setName(mThreadName); - mSyncThread.start(); - } else { - throw new IllegalStateException( - "WebSyncManager can't be created without context"); - } - } - - protected Object clone() throws CloneNotSupportedException { - throw new CloneNotSupportedException("doesn't implement Cloneable"); - } - - public void run() { - // prepare Looper for sync handler - Looper.prepare(); - mHandler = new SyncHandler(); - onSyncInit(); - // lower the priority after onSyncInit() is done - Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); - - Message msg = mHandler.obtainMessage(SYNC_MESSAGE); - mHandler.sendMessageDelayed(msg, SYNC_LATER_INTERVAL); - - Looper.loop(); - } - - /** - * sync() forces sync manager to sync now - */ - public void sync() { - if (Config.LOGV) { - Log.v(LOGTAG, "*** WebSyncManager sync ***"); - } - if (mHandler == null) { - return; - } - mHandler.removeMessages(SYNC_MESSAGE); - Message msg = mHandler.obtainMessage(SYNC_MESSAGE); - mHandler.sendMessageDelayed(msg, SYNC_NOW_INTERVAL); - } - - /** - * resetSync() resets sync manager's timer - */ - public void resetSync() { - if (Config.LOGV) { - Log.v(LOGTAG, "*** WebSyncManager resetSync ***"); - } - if (mHandler == null) { - return; - } - mHandler.removeMessages(SYNC_MESSAGE); - Message msg = mHandler.obtainMessage(SYNC_MESSAGE); - mHandler.sendMessageDelayed(msg, SYNC_LATER_INTERVAL); - } - - /** - * startSync() requests sync manager to start sync - */ - public void startSync() { - if (Config.LOGV) { - Log.v(LOGTAG, "*** WebSyncManager startSync ***, Ref count:" + - mStartSyncRefCount); - } - if (mHandler == null) { - return; - } - if (++mStartSyncRefCount == 1) { - Message msg = mHandler.obtainMessage(SYNC_MESSAGE); - mHandler.sendMessageDelayed(msg, SYNC_LATER_INTERVAL); - } - } - - /** - * stopSync() requests sync manager to stop sync. remove any SYNC_MESSAGE in - * the queue to break the sync loop - */ - public void stopSync() { - if (Config.LOGV) { - Log.v(LOGTAG, "*** WebSyncManager stopSync ***, Ref count:" + - mStartSyncRefCount); - } - if (mHandler == null) { - return; - } - if (--mStartSyncRefCount == 0) { - mHandler.removeMessages(SYNC_MESSAGE); - } - } - - protected void onSyncInit() { - } - - abstract void syncFromRamToFlash(); -} diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java deleted file mode 100644 index 417b657..0000000 --- a/core/java/android/webkit/WebView.java +++ /dev/null @@ -1,5338 +0,0 @@ -/* - * Copyright (C) 2006 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.webkit; - -import android.app.AlertDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.DialogInterface.OnCancelListener; -import android.content.res.TypedArray; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.Path; -import android.graphics.Picture; -import android.graphics.Point; -import android.graphics.Rect; -import android.graphics.Region; -import android.net.http.SslCertificate; -import android.net.Uri; -import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.os.ServiceManager; -import android.os.SystemClock; -import android.provider.Checkin; -import android.text.IClipboard; -import android.text.Selection; -import android.text.Spannable; -import android.util.AttributeSet; -import android.util.Config; -import android.util.EventLog; -import android.util.Log; -import android.view.animation.AlphaAnimation; -import android.view.animation.Animation; -import android.view.animation.AnimationUtils; -import android.view.Gravity; -import android.view.KeyEvent; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.SoundEffectConstants; -import android.view.VelocityTracker; -import android.view.View; -import android.view.ViewConfiguration; -import android.view.ViewGroup; -import android.view.ViewParent; -import android.view.ViewTreeObserver; -import android.view.inputmethod.InputMethodManager; -import android.webkit.TextDialog.AutoCompleteAdapter; -import android.webkit.WebViewCore.EventHub; -import android.widget.AbsoluteLayout; -import android.widget.AdapterView; -import android.widget.ArrayAdapter; -import android.widget.ImageView; -import android.widget.ListView; -import android.widget.RelativeLayout; -import android.widget.Scroller; -import android.widget.Toast; -import android.widget.ZoomControls; -import android.widget.ZoomRingController; -import android.widget.FrameLayout; -import android.widget.AdapterView.OnItemClickListener; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.net.URLDecoder; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; - -/** - * <p>A View that displays web pages. This class is the basis upon which you - * can roll your own web browser or simply display some online content within your Activity. - * It uses the WebKit rendering engine to display - * web pages and includes methods to navigate forward and backward - * through a history, zoom in and out, perform text searches and more.</p> - * <p>Note that, in order for your Activity to access the Internet and load web pages - * in a WebView, you must add the <var>INTERNET</var> permissions to your - * Android Manifest file:</p> - * <pre><uses-permission android:name="android.permission.INTERNET" /></pre> - * <p>This must be a child of the <code><manifest></code> element.</p> - */ -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; - static final boolean LOGV_ENABLED = DEBUG ? Config.LOGD : Config.LOGV; - - private class ExtendedZoomControls extends FrameLayout { - public ExtendedZoomControls(Context context, AttributeSet attrs) { - super(context, attrs); - LayoutInflater inflater = (LayoutInflater) - context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - inflater.inflate(com.android.internal.R.layout.zoom_magnify, this, true); - mZoomControls = (ZoomControls) findViewById(com.android.internal.R.id.zoomControls); - mZoomMagnify = (ImageView) findViewById(com.android.internal.R.id.zoomMagnify); - } - - 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); - } - - public void hide() { - fade(View.GONE, 1.0f, 0.0f); - } - - private void fade(int visibility, float startAlpha, float endAlpha) { - AlphaAnimation anim = new AlphaAnimation(startAlpha, endAlpha); - anim.setDuration(500); - startAnimation(anim); - setVisibility(visibility); - } - - public void setIsZoomMagnifyEnabled(boolean isEnabled) { - mZoomMagnify.setEnabled(isEnabled); - } - - public boolean hasFocus() { - return mZoomControls.hasFocus() || mZoomMagnify.hasFocus(); - } - - public void setOnZoomInClickListener(OnClickListener listener) { - mZoomControls.setOnZoomInClickListener(listener); - } - - public void setOnZoomOutClickListener(OnClickListener listener) { - mZoomControls.setOnZoomOutClickListener(listener); - } - - public void setOnZoomMagnifyClickListener(OnClickListener listener) { - mZoomMagnify.setOnClickListener(listener); - } - - ZoomControls mZoomControls; - ImageView mZoomMagnify; - } - - /** - * Transportation object for returning WebView across thread boundaries. - */ - public class WebViewTransport { - private WebView mWebview; - - /** - * Set the WebView to the transportation object. - * @param webview The WebView to transport. - */ - public synchronized void setWebView(WebView webview) { - mWebview = webview; - } - - /** - * Return the WebView object. - * @return WebView The transported WebView object. - */ - public synchronized WebView getWebView() { - return mWebview; - } - } - - // A final CallbackProxy shared by WebViewCore and BrowserFrame. - private final CallbackProxy mCallbackProxy; - - private final WebViewDatabase mDatabase; - - // SSL certificate for the main top-level page (if secure) - private SslCertificate mCertificate; - - // Native WebView pointer that is 0 until the native object has been - // created. - private int mNativeClass; - // This would be final but it needs to be set to null when the WebView is - // destroyed. - private WebViewCore mWebViewCore; - // Handler for dispatching UI messages. - /* package */ final Handler mPrivateHandler = new PrivateHandler(); - private TextDialog mTextEntry; - // Used to ignore changes to webkit text that arrives to the UI side after - // more key events. - private int mTextGeneration; - - // The list of loaded plugins. - private static PluginList sPluginList; - - /** - * Position of the last touch event. - */ - private float mLastTouchX; - private float mLastTouchY; - - /** - * Time of the last touch event. - */ - 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; - - private static boolean mShowZoomRingTutorial = true; - private static final int ZOOM_RING_TUTORIAL_DURATION = 3000; - - /** - * Touch mode - */ - private int mTouchMode = TOUCH_DONE_MODE; - private static final int TOUCH_INIT_MODE = 1; - private static final int TOUCH_DRAG_START_MODE = 2; - private static final int TOUCH_DRAG_MODE = 3; - private static final int TOUCH_SHORTPRESS_START_MODE = 4; - 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; - private static final int SCROLL_ZOOM_ANIMATION_OUT = 10; - private static final int SCROLL_ZOOM_OUT = 11; - 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; - - // Whether we are in the drag tap mode, which exists starting at the second - // tap's down, through its move, and includes its up. These events should be - // given to the method on the zoom controller. - private boolean mInZoomTapDragMode = false; - - // Whether to prevent drag during touch. The initial value depends on - // mForwardTouchEvents. If WebCore wants touch events, we assume it will - // take control of touch events unless it says no for touch down event. - private boolean mPreventDrag; - - // 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; - - // Whether or not to draw the focus ring. - private boolean mDrawFocusRing = true; - - /** - * Customizable constant - */ - // pre-computed square of ViewConfiguration.getTouchSlop() - private int mTouchSlopSquare; - // This should be ViewConfiguration.getTapTimeout() - // But system time out is 100ms, which is too short for the browser. - // In the browser, if it switches out of tap too soon, jump tap won't work. - private static final int TAP_TIMEOUT = 200; - // The duration in milliseconds we will wait to see if it is a double tap. - private static final int DOUBLE_TAP_TIMEOUT = 250; - // This should be ViewConfiguration.getLongPressTimeout() - // But system time out is 500ms, which is too short for the browser. - // With a short timeout, it's difficult to treat trigger a short press. - private static final int LONG_PRESS_TIMEOUT = 1000; - // needed to avoid flinging after a pause of no movement - private static final int MIN_FLING_TIME = 250; - // The time that the Zoom Controls are visible before fading away - private static final long ZOOM_CONTROLS_TIMEOUT = - ViewConfiguration.getZoomControlsTimeout(); - // The amount of content to overlap between two screens when going through - // pages with the space bar, in pixels. - private static final int PAGE_SCROLL_OVERLAP = 24; - - /** - * These prevent calling requestLayout if either dimension is fixed. This - * depends on the layout parameters and the measure specs. - */ - boolean mWidthCanMeasure; - boolean mHeightCanMeasure; - - // Remember the last dimensions we sent to the native side so we can avoid - // sending the same dimensions more than once. - int mLastWidthSent; - int mLastHeightSent; - - private int mContentWidth; // cache of value from WebViewCore - private int mContentHeight; // cache of value from WebViewCore - - static int MAX_FLOAT_CONTENT_WIDTH = 480; - private int mMinContentWidth; - - // Need to have the separate control for horizontal and vertical scrollbar - // style than the View's single scrollbar style - private boolean mOverlayHorizontalScrollbar = true; - private boolean mOverlayVerticalScrollbar = false; - - // our standard speed. this way small distances will be traversed in less - // time than large distances, but we cap the duration, so that very large - // distances won't take too long to get there. - private static final int STD_SPEED = 480; // pixels per second - // time for the longest scroll animation - private static final int MAX_DURATION = 750; // milliseconds - private Scroller mScroller; - - private boolean mWrapContent; - - // The View containing the zoom controls - private ExtendedZoomControls mZoomControls; - private Runnable mZoomControlRunnable; - - // true if we should call webcore to draw the content, false means we have - // requested something but it isn't ready to draw yet. - private WebViewCore.FocusData mFocusData; - /** - * Private message ids - */ - private static final int REMEMBER_PASSWORD = 1; - private static final int NEVER_REMEMBER_PASSWORD = 2; - private static final int SWITCH_TO_SHORTPRESS = 3; - private static final int SWITCH_TO_LONGPRESS = 4; - private static final int RELEASE_SINGLE_TAP = 5; - private static final int UPDATE_TEXT_ENTRY_ADAPTER = 6; - private static final int SWITCH_TO_ENTER = 7; - private static final int RESUME_WEBCORE_UPDATE = 8; - private static final int DISMISS_ZOOM_RING_TUTORIAL = 9; - - //! arg1=x, arg2=y - static final int SCROLL_TO_MSG_ID = 10; - static final int SCROLL_BY_MSG_ID = 11; - //! arg1=x, arg2=y - static final int SPAWN_SCROLL_TO_MSG_ID = 12; - //! arg1=x, arg2=y - static final int SYNC_SCROLL_TO_MSG_ID = 13; - static final int NEW_PICTURE_MSG_ID = 14; - static final int UPDATE_TEXT_ENTRY_MSG_ID = 15; - static final int WEBCORE_INITIALIZED_MSG_ID = 16; - static final int UPDATE_TEXTFIELD_TEXT_MSG_ID = 17; - static final int DID_FIRST_LAYOUT_MSG_ID = 18; - static final int RECOMPUTE_FOCUS_MSG_ID = 19; - 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_ENTER = 23; - static final int PREVENT_TOUCH_ID = 24; - static final int WEBCORE_NEED_TOUCH_EVENTS = 25; - // obj=Rect in doc coordinates - static final int INVAL_RECT_MSG_ID = 26; - - static final String[] HandlerDebugString = { - "REMEMBER_PASSWORD", // = 1; - "NEVER_REMEMBER_PASSWORD", // = 2; - "SWITCH_TO_SHORTPRESS", // = 3; - "SWITCH_TO_LONGPRESS", // = 4; - "RELEASE_SINGLE_TAP", // = 5; - "UPDATE_TEXT_ENTRY_ADAPTER", // = 6; - "SWITCH_TO_ENTER", // = 7; - "RESUME_WEBCORE_UPDATE", // = 8; - "9", - "SCROLL_TO_MSG_ID", // = 10; - "SCROLL_BY_MSG_ID", // = 11; - "SPAWN_SCROLL_TO_MSG_ID", // = 12; - "SYNC_SCROLL_TO_MSG_ID", // = 13; - "NEW_PICTURE_MSG_ID", // = 14; - "UPDATE_TEXT_ENTRY_MSG_ID", // = 15; - "WEBCORE_INITIALIZED_MSG_ID", // = 16; - "UPDATE_TEXTFIELD_TEXT_MSG_ID", // = 17; - "DID_FIRST_LAYOUT_MSG_ID", // = 18; - "RECOMPUTE_FOCUS_MSG_ID", // = 19; - "NOTIFY_FOCUS_SET_MSG_ID", // = 20; - "MARK_NODE_INVALID_ID", // = 21; - "UPDATE_CLIPBOARD", // = 22; - "LONG_PRESS_ENTER", // = 23; - "PREVENT_TOUCH_ID", // = 24; - "WEBCORE_NEED_TOUCH_EVENTS", // = 25; - "INVAL_RECT_MSG_ID" // = 26; - }; - - // width which view is considered to be fully zoomed out - static final int ZOOM_OUT_WIDTH = 1024; - - private static final float MAX_ZOOM_RING_ANGLE = (float) (Math.PI * 2 / 3); - private static final int ZOOM_RING_STEPS = 4; - private static final float ZOOM_RING_ANGLE_UNIT = MAX_ZOOM_RING_ANGLE - / ZOOM_RING_STEPS; - - private static final float DEFAULT_MAX_ZOOM_SCALE = 2; - private static final float DEFAULT_MIN_ZOOM_SCALE = (float) 1/3; - // scale limit, which can be set through viewport meta tag in the web page - private float mMaxZoomScale = DEFAULT_MAX_ZOOM_SCALE; - private float mMinZoomScale = DEFAULT_MIN_ZOOM_SCALE; - - // initial scale in percent. 0 means using default. - private int mInitialScale = 0; - - // set to true temporarily while the zoom control is being dragged - private boolean mPreviewZoomOnly = false; - - // computed scale and inverse, from mZoomWidth. - private float mActualScale = 1; - private float mInvActualScale = 1; - // if this is non-zero, it is used on drawing rather than mActualScale - private float mZoomScale; - private float mInvInitialZoomScale; - private float mInvFinalZoomScale; - private long mZoomStart; - private static final int ZOOM_ANIMATION_LENGTH = 500; - - private boolean mUserScroll = false; - - private int mSnapScrollMode = SNAP_NONE; - private static final int SNAP_NONE = 1; - private static final int SNAP_X = 2; - private static final int SNAP_Y = 3; - private static final int SNAP_X_LOCK = 4; - private static final int SNAP_Y_LOCK = 5; - private boolean mSnapPositive; - - // Used to match key downs and key ups - private boolean mGotKeyDown; - - /* package */ static boolean mLogEvent = true; - private static final int EVENT_LOG_ZOOM_LEVEL_CHANGE = 70101; - private static final int EVENT_LOG_DOUBLE_TAP_DURATION = 70102; - - // for event log - private long mLastTouchUpTime = 0; - - /** - * URI scheme for telephone number - */ - public static final String SCHEME_TEL = "tel:"; - /** - * URI scheme for email address - */ - public static final String SCHEME_MAILTO = "mailto:"; - /** - * URI scheme for map address - */ - public static final String SCHEME_GEO = "geo:0,0?q="; - - private int mBackgroundColor = Color.WHITE; - - // Used to notify listeners of a new picture. - private PictureListener mPictureListener; - /** - * Interface to listen for new pictures as they change. - */ - public interface PictureListener { - /** - * Notify the listener that the picture has changed. - * @param view The WebView that owns the picture. - * @param picture The new picture. - */ - public void onNewPicture(WebView view, Picture picture); - } - - public class HitTestResult { - /** - * Default HitTestResult, where the target is unknown - */ - public static final int UNKNOWN_TYPE = 0; - /** - * HitTestResult for hitting a HTML::a tag - */ - public static final int ANCHOR_TYPE = 1; - /** - * HitTestResult for hitting a phone number - */ - public static final int PHONE_TYPE = 2; - /** - * HitTestResult for hitting a map address - */ - public static final int GEO_TYPE = 3; - /** - * HitTestResult for hitting an email address - */ - public static final int EMAIL_TYPE = 4; - /** - * HitTestResult for hitting an HTML::img tag - */ - public static final int IMAGE_TYPE = 5; - /** - * HitTestResult for hitting a HTML::a tag which contains HTML::img - */ - public static final int IMAGE_ANCHOR_TYPE = 6; - /** - * HitTestResult for hitting a HTML::a tag with src=http - */ - public static final int SRC_ANCHOR_TYPE = 7; - /** - * HitTestResult for hitting a HTML::a tag with src=http + HTML::img - */ - public static final int SRC_IMAGE_ANCHOR_TYPE = 8; - /** - * HitTestResult for hitting an edit text area - */ - public static final int EDIT_TEXT_TYPE = 9; - - private int mType; - private String mExtra; - - HitTestResult() { - mType = UNKNOWN_TYPE; - } - - private void setType(int type) { - mType = type; - } - - private void setExtra(String extra) { - mExtra = extra; - } - - public int getType() { - return mType; - } - - public String getExtra() { - return mExtra; - } - } - - private ZoomRingController mZoomRingController; - private ImageView mZoomRingOverview; - private Animation mZoomRingOverviewExitAnimation; - - // These keep track of the center point of the zoom ring. They are used to - // determine the point around which we should zoom. - private float mZoomCenterX; - private float mZoomCenterY; - - private ZoomRingController.OnZoomListener mZoomListener = - new ZoomRingController.OnZoomListener() { - - private float mClockwiseBound; - private float mCounterClockwiseBound; - private float mStartScale; - - public void onCenter(int x, int y) { - // Don't translate when the control is invoked, hence we do nothing - // in this callback - } - - public void onBeginPan() { - setZoomOverviewVisible(false); - if (mLogEvent) { - Checkin.updateStats(mContext.getContentResolver(), - Checkin.Stats.Tag.BROWSER_ZOOM_RING_DRAG, 1, 0.0); - } - } - - public boolean onPan(int deltaX, int deltaY) { - return pinScrollBy(deltaX, deltaY, false, 0); - } - - public void onEndPan() { - } - - public void onVisibilityChanged(boolean visible) { - if (visible) { - switchOutDrawHistory(); - if (mMaxZoomScale - 1 > ZOOM_RING_STEPS * 0.01f) { - mClockwiseBound = (float) (2 * Math.PI - MAX_ZOOM_RING_ANGLE); - } else { - mClockwiseBound = (float) (2 * Math.PI); - } - mZoomRingController.setThumbClockwiseBound(mClockwiseBound); - if (1 - mMinZoomScale > ZOOM_RING_STEPS * 0.01f) { - mCounterClockwiseBound = MAX_ZOOM_RING_ANGLE; - } else { - mCounterClockwiseBound = 0; - } - mZoomRingController - .setThumbCounterclockwiseBound(mCounterClockwiseBound); - float angle = 0f; - if (mActualScale > 1 && mClockwiseBound < (float) (2 * Math.PI)) { - angle = -(float) Math.round(ZOOM_RING_STEPS - * (mActualScale - 1) / (mMaxZoomScale - 1)) - / ZOOM_RING_STEPS; - } else if (mActualScale < 1 && mCounterClockwiseBound > 0) { - angle = (float) Math.round(ZOOM_RING_STEPS - * (1 - mActualScale) / (1 - mMinZoomScale)) - / ZOOM_RING_STEPS; - } - mZoomRingController.setThumbAngle(angle * MAX_ZOOM_RING_ANGLE); - - // Show the zoom overview tab on the ring - setZoomOverviewVisible(true); - if (mLogEvent) { - Checkin.updateStats(mContext.getContentResolver(), - Checkin.Stats.Tag.BROWSER_ZOOM_RING, 1, 0.0); - } - } - } - - public void onBeginDrag() { - mPreviewZoomOnly = true; - mStartScale = mActualScale; - setZoomOverviewVisible(false); - } - - public void onEndDrag() { - mPreviewZoomOnly = false; - if (mLogEvent) { - EventLog.writeEvent(EVENT_LOG_ZOOM_LEVEL_CHANGE, - (int) mStartScale * 100, (int) mActualScale * 100, - System.currentTimeMillis()); - } - setNewZoomScale(mActualScale, true); - } - - public boolean onDragZoom(int deltaZoomLevel, int centerX, - int centerY, float startAngle, float curAngle) { - if (deltaZoomLevel < 0 - && Math.abs(mActualScale - mMinZoomScale) < 0.01f - || deltaZoomLevel > 0 - && Math.abs(mActualScale - mMaxZoomScale) < 0.01f - || deltaZoomLevel == 0) { - return false; - } - mZoomCenterX = (float) centerX; - mZoomCenterY = (float) centerY; - - float scale = 1.0f; - // curAngle is [0, 2 * Math.PI) - if (curAngle < (float) Math.PI) { - if (curAngle >= mCounterClockwiseBound) { - scale = mMinZoomScale; - } else { - scale = 1 - (float) Math.round(curAngle - / ZOOM_RING_ANGLE_UNIT) / ZOOM_RING_STEPS - * (1 - mMinZoomScale); - } - } else { - if (curAngle <= mClockwiseBound) { - scale = mMaxZoomScale; - } else { - scale = 1 + (float) Math.round( - ((float) 2 * Math.PI - curAngle) - / ZOOM_RING_ANGLE_UNIT) / ZOOM_RING_STEPS - * (mMaxZoomScale - 1); - } - } - zoomWithPreview(scale); - return true; - } - - public void onSimpleZoom(boolean zoomIn) { - if (zoomIn) { - zoomIn(); - } else { - zoomOut(); - } - } - - }; - - /** - * Construct a new WebView with a Context object. - * @param context A Context object used to access application assets. - */ - public WebView(Context context) { - this(context, null); - } - - /** - * Construct a new WebView with layout parameters. - * @param context A Context object used to access application assets. - * @param attrs An AttributeSet passed to our parent. - */ - public WebView(Context context, AttributeSet attrs) { - this(context, attrs, com.android.internal.R.attr.webViewStyle); - } - - /** - * Construct a new WebView with layout parameters and a default style. - * @param context A Context object used to access application assets. - * @param attrs An AttributeSet passed to our parent. - * @param defStyle The default style resource ID. - */ - public WebView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - init(); - - mCallbackProxy = new CallbackProxy(context, this); - mWebViewCore = new WebViewCore(context, this, mCallbackProxy); - mDatabase = WebViewDatabase.getInstance(context); - mFocusData = new WebViewCore.FocusData(); - mFocusData.mFrame = 0; - mFocusData.mNode = 0; - mFocusData.mX = 0; - mFocusData.mY = 0; - mScroller = new Scroller(context); - mZoomRingController = new ZoomRingController(context, this); - mZoomRingController.setResetThumbAutomatically(false); - mZoomRingController.setCallback(mZoomListener); - mZoomRingController.setZoomRingTrack( - com.android.internal.R.drawable.zoom_ring_track_absolute); - mZoomRingController.setPannerAcceleration(160); - mZoomRingController.setPannerStartAcceleratingDuration(700); - createZoomRingOverviewTab(); - } - - private void init() { - setWillNotDraw(false); - setFocusable(true); - setFocusableInTouchMode(true); - setClickable(true); - setLongClickable(true); - - final int slop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); - mTouchSlopSquare = slop * slop; - mMinLockSnapReverseDistance = slop; - } - - private void createZoomRingOverviewTab() { - Context context = getContext(); - - mZoomRingOverviewExitAnimation = AnimationUtils.loadAnimation(context, - com.android.internal.R.anim.fade_out); - - mZoomRingOverview = new ImageView(context); - mZoomRingOverview.setBackgroundResource( - com.android.internal.R.drawable.zoom_ring_overview_tab); - mZoomRingOverview.setImageResource(com.android.internal.R.drawable.btn_zoom_page); - - FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams( - FrameLayout.LayoutParams.WRAP_CONTENT, - FrameLayout.LayoutParams.WRAP_CONTENT, - Gravity.CENTER); - // TODO: magic constant that's based on the zoom ring radius + some offset - lp.topMargin = 208; - mZoomRingOverview.setLayoutParams(lp); - mZoomRingOverview.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - // Hide the zoom ring - mZoomRingController.setVisible(false); - if (mLogEvent) { - Checkin.updateStats(mContext.getContentResolver(), - Checkin.Stats.Tag.BROWSER_ZOOM_OVERVIEW, 1, 0.0); - } - zoomScrollOut(); - }}); - - // Measure the overview View to figure out its height - mZoomRingOverview.forceLayout(); - mZoomRingOverview.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), - MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); - - ViewGroup container = mZoomRingController.getContainer(); - // Find the index of the zoom ring in the container - View zoomRing = container.findViewById(mZoomRingController.getZoomRingId()); - int zoomRingIndex; - for (zoomRingIndex = container.getChildCount() - 1; zoomRingIndex >= 0; zoomRingIndex--) { - if (container.getChildAt(zoomRingIndex) == zoomRing) break; - } - // Add the overview tab below the zoom ring (so we don't steal its events) - container.addView(mZoomRingOverview, zoomRingIndex); - // Since we use margins to adjust the vertical placement of the tab, the widget - // ends up getting clipped off. Ensure the container is big enough for - // us. - int myHeight = mZoomRingOverview.getMeasuredHeight() + lp.topMargin / 2; - // Multiplied by 2 b/c the zoom ring needs to be centered on the screen - container.setMinimumHeight(myHeight * 2); - } - - private void setZoomOverviewVisible(boolean visible) { - int newVisibility = visible ? View.VISIBLE : View.INVISIBLE; - if (mZoomRingOverview.getVisibility() == newVisibility) return; - - if (!visible) { - mZoomRingOverview.startAnimation(mZoomRingOverviewExitAnimation); - } - mZoomRingOverview.setVisibility(newVisibility); - } - - /* 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(schemePlusHost, username, password); - } else { - final Message remember = mPrivateHandler.obtainMessage( - REMEMBER_PASSWORD); - 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", schemePlusHost); - neverRemember.getData().putString("username", username); - neverRemember.getData().putString("password", password); - neverRemember.obj = resumeMsg; - - new AlertDialog.Builder(getContext()) - .setTitle(com.android.internal.R.string.save_password_label) - .setMessage(com.android.internal.R.string.save_password_message) - .setPositiveButton(com.android.internal.R.string.save_password_notnow, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - resumeMsg.sendToTarget(); - } - }) - .setNeutralButton(com.android.internal.R.string.save_password_remember, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - remember.sendToTarget(); - } - }) - .setNegativeButton(com.android.internal.R.string.save_password_never, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - neverRemember.sendToTarget(); - } - }) - .setOnCancelListener(new OnCancelListener() { - public void onCancel(DialogInterface dialog) { - resumeMsg.sendToTarget(); - } - }).show(); - // Return true so that WebViewCore will pause while the dialog is - // up. - rVal = true; - } - return rVal; - } - - @Override - public void setScrollBarStyle(int style) { - if (style == View.SCROLLBARS_INSIDE_INSET - || style == View.SCROLLBARS_OUTSIDE_INSET) { - mOverlayHorizontalScrollbar = mOverlayVerticalScrollbar = false; - } else { - mOverlayHorizontalScrollbar = mOverlayVerticalScrollbar = true; - } - super.setScrollBarStyle(style); - } - - /** - * Specify whether the horizontal scrollbar has overlay style. - * @param overlay TRUE if horizontal scrollbar should have overlay style. - */ - public void setHorizontalScrollbarOverlay(boolean overlay) { - mOverlayHorizontalScrollbar = overlay; - } - - /** - * Specify whether the vertical scrollbar has overlay style. - * @param overlay TRUE if vertical scrollbar should have overlay style. - */ - public void setVerticalScrollbarOverlay(boolean overlay) { - mOverlayVerticalScrollbar = overlay; - } - - /** - * Return whether horizontal scrollbar has overlay style - * @return TRUE if horizontal scrollbar has overlay style. - */ - public boolean overlayHorizontalScrollbar() { - return mOverlayHorizontalScrollbar; - } - - /** - * Return whether vertical scrollbar has overlay style - * @return TRUE if vertical scrollbar has overlay style. - */ - public boolean overlayVerticalScrollbar() { - return mOverlayVerticalScrollbar; - } - - /* - * Return the width of the view where the content of WebView should render - * to. - */ - private int getViewWidth() { - if (!isVerticalScrollBarEnabled() || mOverlayVerticalScrollbar) { - return getWidth(); - } else { - return getWidth() - getVerticalScrollbarWidth(); - } - } - - /* - * Return the height of the view where the content of WebView should render - * to. - */ - private int getViewHeight() { - if (!isHorizontalScrollBarEnabled() || mOverlayHorizontalScrollbar) { - return getHeight(); - } else { - return getHeight() - getHorizontalScrollbarHeight(); - } - } - - /** - * @return The SSL certificate for the main top-level page or null if - * there is no certificate (the site is not secure). - */ - public SslCertificate getCertificate() { - return mCertificate; - } - - /** - * Sets the SSL certificate for the main top-level page. - */ - public void setCertificate(SslCertificate certificate) { - // here, the certificate can be null (if the site is not secure) - mCertificate = certificate; - } - - //------------------------------------------------------------------------- - // Methods called by activity - //------------------------------------------------------------------------- - - /** - * Save the username and password for a particular host in the WebView's - * internal database. - * @param host The host that required the credentials. - * @param username The username for the given host. - * @param password The password for the given host. - */ - public void savePassword(String host, String username, String password) { - mDatabase.setUsernamePassword(host, username, password); - } - - /** - * Set the HTTP authentication credentials for a given host and realm. - * - * @param host The host for the credentials. - * @param realm The realm for the credentials. - * @param username The username for the password. If it is null, it means - * password can't be saved. - * @param password The password - */ - public void setHttpAuthUsernamePassword(String host, String realm, - String username, String password) { - mDatabase.setHttpAuthUsernamePassword(host, realm, username, password); - } - - /** - * Retrieve the HTTP authentication username and password for a given - * host & realm pair - * - * @param host The host for which the credentials apply. - * @param realm The realm for which the credentials apply. - * @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. - */ - public String[] getHttpAuthUsernamePassword(String host, String realm) { - return mDatabase.getHttpAuthUsernamePassword(host, realm); - } - - /** - * Destroy the internal state of the WebView. This method should be called - * after the WebView has been removed from the view system. No other - * methods may be called on a WebView after destroy. - */ - public void destroy() { - clearTextEntry(); - if (mWebViewCore != null) { - // Set the handlers to null before destroying WebViewCore so no - // more messages will be posted. - mCallbackProxy.setWebViewClient(null); - mCallbackProxy.setWebChromeClient(null); - // Tell WebViewCore to destroy itself - WebViewCore webViewCore = mWebViewCore; - mWebViewCore = null; // prevent using partial webViewCore - webViewCore.destroy(); - // Remove any pending messages that might not be serviced yet. - mPrivateHandler.removeCallbacksAndMessages(null); - mCallbackProxy.removeCallbacksAndMessages(null); - // Wake up the WebCore thread just in case it is waiting for a - // javascript dialog. - synchronized (mCallbackProxy) { - mCallbackProxy.notify(); - } - } - if (mNativeClass != 0) { - nativeDestroy(); - mNativeClass = 0; - } - } - - /** - * Enables platform notifications of data state and proxy changes. - */ - public static void enablePlatformNotifications() { - Network.enablePlatformNotifications(); - } - - /** - * If platform notifications are enabled, this should be called - * from onPause() or onStop(). - */ - 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 - * {@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(); - final int currentIndex = list.getCurrentIndex(); - final int size = list.getSize(); - // We should fail saving the state if the list is empty or the index is - // not in a valid range. - if (currentIndex < 0 || currentIndex >= size || size == 0) { - return null; - } - outState.putInt("index", currentIndex); - // FIXME: This should just be a byte[][] instead of ArrayList but - // Parcel.java does not have the code to handle multi-dimensional - // arrays. - ArrayList<byte[]> history = new ArrayList<byte[]>(size); - for (int i = 0; i < size; i++) { - WebHistoryItem item = list.getItemAtIndex(i); - byte[] data = item.getFlattenedData(); - if (data == null) { - // It would be very odd to not have any data for a given history - // item. And we will fail to rebuild the history list without - // flattened data. - return null; - } - history.add(data); - } - outState.putSerializable("history", history); - if (mCertificate != null) { - outState.putBundle("certificate", - SslCertificate.saveState(mCertificate)); - } - return list; - } - - /** - * 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 - * {@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")); - - final WebBackForwardList list = mCallbackProxy.getBackForwardList(); - final int index = inState.getInt("index"); - // We can't use a clone of the list because we need to modify the - // shared copy, so synchronize instead to prevent concurrent - // modifications. - synchronized (list) { - final List<byte[]> history = - (List<byte[]>) inState.getSerializable("history"); - final int size = history.size(); - // Check the index bounds so we don't crash in native code while - // restoring the history index. - if (index < 0 || index >= size) { - return null; - } - for (int i = 0; i < size; i++) { - byte[] data = history.remove(0); - if (data == null) { - // If we somehow have null data, we cannot reconstruct - // the item and thus our history list cannot be rebuilt. - return null; - } - WebHistoryItem item = new WebHistoryItem(data); - list.addHistoryItem(item); - } - // Grab the most recent copy to return to the caller. - returnList = copyBackForwardList(); - // Update the copy to have the correct index. - returnList.setCurrentIndex(index); - } - // Remove all pending messages because we are restoring previous - // state. - mWebViewCore.removeMessages(); - // Send a restore state message. - mWebViewCore.sendMessage(EventHub.RESTORE_STATE, index); - } - return returnList; - } - - /** - * Load the given url. - * @param url The url of the resource to load. - */ - public void loadUrl(String url) { - switchOutDrawHistory(); - mWebViewCore.sendMessage(EventHub.LOAD_URL, url); - clearTextEntry(); - } - - /** - * Load the given data into the WebView. This will load the data into - * WebView using the data: scheme. Content loaded through this mechanism - * does not have the ability to load content from the network. - * @param data A String of data in the given encoding. - * @param mimeType The MIMEType of the data. i.e. text/html, image/jpeg - * @param encoding The encoding of the data. i.e. utf-8, base64 - */ - public void loadData(String data, String mimeType, String encoding) { - loadUrl("data:" + mimeType + ";" + encoding + "," + data); - } - - /** - * 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. - * <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" - * @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" - * @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. - */ - public void loadDataWithBaseURL(String baseUrl, String data, - String mimeType, String encoding, String failUrl) { - - if (baseUrl != null && baseUrl.toLowerCase().startsWith("data:")) { - loadData(data, mimeType, encoding); - return; - } - switchOutDrawHistory(); - HashMap arg = new HashMap(); - arg.put("baseUrl", baseUrl); - arg.put("data", data); - arg.put("mimeType", mimeType); - arg.put("encoding", encoding); - arg.put("failUrl", failUrl); - mWebViewCore.sendMessage(EventHub.LOAD_DATA, arg); - clearTextEntry(); - } - - /** - * Stop the current load. - */ - public void stopLoading() { - // TODO: should we clear all the messages in the queue before sending - // STOP_LOADING? - switchOutDrawHistory(); - mWebViewCore.sendMessage(EventHub.STOP_LOADING); - } - - /** - * Reload the current url. - */ - public void reload() { - switchOutDrawHistory(); - mWebViewCore.sendMessage(EventHub.RELOAD); - } - - /** - * Return true if this WebView has a back history item. - * @return True iff this WebView has a back history item. - */ - public boolean canGoBack() { - WebBackForwardList l = mCallbackProxy.getBackForwardList(); - synchronized (l) { - if (l.getClearPending()) { - return false; - } else { - return l.getCurrentIndex() > 0; - } - } - } - - /** - * Go back in the history of this WebView. - */ - public void goBack() { - goBackOrForward(-1); - } - - /** - * Return true if this WebView has a forward history item. - * @return True iff this Webview has a forward history item. - */ - public boolean canGoForward() { - WebBackForwardList l = mCallbackProxy.getBackForwardList(); - synchronized (l) { - if (l.getClearPending()) { - return false; - } else { - return l.getCurrentIndex() < l.getSize() - 1; - } - } - } - - /** - * Go forward in the history of this WebView. - */ - public void goForward() { - goBackOrForward(1); - } - - /** - * Return true if the page can go back or forward the given - * number of steps. - * @param steps The negative or positive number of steps to move the - * history. - */ - public boolean canGoBackOrForward(int steps) { - WebBackForwardList l = mCallbackProxy.getBackForwardList(); - synchronized (l) { - if (l.getClearPending()) { - return false; - } else { - int newIndex = l.getCurrentIndex() + steps; - return newIndex >= 0 && newIndex < l.getSize(); - } - } - } - - /** - * Go to the history item that is the number of steps away from - * the current item. Steps is negative if backward and positive - * if forward. - * @param steps The number of steps to take back or forward in the back - * forward list. - */ - public void goBackOrForward(int steps) { - goBackOrForward(steps, false); - } - - private void goBackOrForward(int steps, boolean ignoreSnapshot) { - // every time we go back or forward, we want to reset the - // WebView certificate: - // if the new site is secure, we will reload it and get a - // new certificate set; - // if the new site is not secure, the certificate must be - // null, and that will be the case - mCertificate = null; - if (steps != 0) { - clearTextEntry(); - mWebViewCore.sendMessage(EventHub.GO_BACK_FORWARD, steps, - ignoreSnapshot ? 1 : 0); - } - } - - private boolean extendScroll(int y) { - int finalY = mScroller.getFinalY(); - int newY = pinLocY(finalY + y); - if (newY == finalY) return false; - mScroller.setFinalY(newY); - mScroller.extendDuration(computeDuration(0, y)); - return true; - } - - /** - * Scroll the contents of the view up by half the view size - * @param top true to jump to the top of the page - * @return true if the page was scrolled - */ - public boolean pageUp(boolean top) { - if (mNativeClass == 0) { - return false; - } - nativeClearFocus(-1, -1); - if (top) { - // go to the top of the document - return pinScrollTo(mScrollX, 0, true, 0); - } - // Page up - int h = getHeight(); - int y; - if (h > 2 * PAGE_SCROLL_OVERLAP) { - y = -h + PAGE_SCROLL_OVERLAP; - } else { - y = -h / 2; - } - mUserScroll = true; - return mScroller.isFinished() ? pinScrollBy(0, y, true, 0) - : extendScroll(y); - } - - /** - * Scroll the contents of the view down by half the page size - * @param bottom true to jump to bottom of page - * @return true if the page was scrolled - */ - public boolean pageDown(boolean bottom) { - if (mNativeClass == 0) { - return false; - } - nativeClearFocus(-1, -1); - if (bottom) { - return pinScrollTo(mScrollX, mContentHeight, true, 0); - } - // Page down. - int h = getHeight(); - int y; - if (h > 2 * PAGE_SCROLL_OVERLAP) { - y = h - PAGE_SCROLL_OVERLAP; - } else { - y = h / 2; - } - mUserScroll = true; - return mScroller.isFinished() ? pinScrollBy(0, y, true, 0) - : extendScroll(y); - } - - /** - * Clear the view so that onDraw() will draw nothing but white background, - * and onMeasure() will return 0 if MeasureSpec is not MeasureSpec.EXACTLY - */ - public void clearView() { - mContentWidth = 0; - mContentHeight = 0; - mWebViewCore.sendMessage(EventHub.CLEAR_CONTENT); - } - - /** - * Return a new picture that captures the current display of the webview. - * This is a copy of the display, and will be unaffected if the webview - * later loads a different URL. - * - * @return a picture containing the current contents of the view. Note this - * picture is of the entire document, and is not restricted to the - * bounds of the view. - */ - public Picture capturePicture() { - if (null == mWebViewCore) return null; // check for out of memory tab - return mWebViewCore.copyContentPicture(); - } - - /** - * Return true if the browser is displaying a TextView for text input. - */ - private boolean inEditingMode() { - return mTextEntry != null && mTextEntry.getParent() != null - && mTextEntry.hasFocus(); - } - - private void clearTextEntry() { - if (inEditingMode()) { - mTextEntry.remove(); - } - } - - /** - * Return the current scale of the WebView - * @return The current scale. - */ - public float getScale() { - return mActualScale; - } - - /** - * Set the initial scale for the WebView. 0 means default. If - * {@link WebSettings#getUseWideViewPort()} is true, it zooms out all the - * way. Otherwise it starts with 100%. If initial scale is greater than 0, - * WebView starts will this value as initial scale. - * - * @param scaleInPercent The initial scale in percent. - */ - public void setInitialScale(int scaleInPercent) { - mInitialScale = scaleInPercent; - } - - /** - * Invoke the graphical zoom picker widget for this WebView. This will - * result in the zoom widget appearing on the screen to control the zoom - * level of this WebView. - */ - public void invokeZoomPicker() { - if (!getSettings().supportZoom()) { - Log.w(LOGTAG, "This WebView doesn't support zoom."); - return; - } - clearTextEntry(); - ExtendedZoomControls zoomControls = (ExtendedZoomControls) - getZoomControls(); - zoomControls.show(true, canZoomScrollOut()); - zoomControls.requestFocus(); - mPrivateHandler.removeCallbacks(mZoomControlRunnable); - mPrivateHandler.postDelayed(mZoomControlRunnable, - ZOOM_CONTROLS_TIMEOUT); - } - - /** - * Return a HitTestResult based on the current focus node. If a HTML::a tag - * 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. - * If an email address is found, the HitTestResult type is set to EMAIL_TYPE - * and the email is set in the "extra" field of HitTestResult. Otherwise, - * HitTestResult type is set to UNKNOWN_TYPE. - */ - public HitTestResult getHitTestResult() { - if (mNativeClass == 0) { - return null; - } - - HitTestResult result = new HitTestResult(); - - if (nativeUpdateFocusNode()) { - FocusNode node = mFocusNode; - if (node.mIsTextField || node.mIsTextArea) { - result.setType(HitTestResult.EDIT_TEXT_TYPE); - } else if (node.mText != null) { - String text = node.mText; - 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.SRC_ANCHOR_TYPE) { - // Now check to see if it is an image. - int contentX = viewToContent((int) mLastTouchX + mScrollX); - int contentY = viewToContent((int) mLastTouchY + mScrollY); - String text = nativeImageURI(contentX, contentY); - if (text != null) { - result.setType(type == HitTestResult.UNKNOWN_TYPE ? - HitTestResult.IMAGE_TYPE : - HitTestResult.SRC_IMAGE_ANCHOR_TYPE); - result.setExtra(text); - } - } - return result; - } - - /** - * Request the href of an anchor element due to getFocusNodePath returning - * "href." If hrefMsg is null, this method returns immediately and does not - * dispatch hrefMsg to its target. - * - * @param hrefMsg This message will be dispatched with the result of the - * request as the data member with "url" as key. The result can - * be null. - */ - public void requestFocusNodeHref(Message hrefMsg) { - if (hrefMsg == null || mNativeClass == 0) { - return; - } - 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. - * - * @param msg This message will be dispatched with the result of the request - * as the data member with "url" as key. The result can be null. - */ - public void requestImageRef(Message msg) { - int contentX = viewToContent((int) mLastTouchX + mScrollX); - int contentY = viewToContent((int) mLastTouchY + mScrollY); - 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) { -// Log.d(LOGTAG, "-- pinLoc " + x + " " + viewMax + " " + docMax); - if (docMax < viewMax) { // the doc has room on the sides for "blank" - x = -(viewMax - docMax) >> 1; -// Log.d(LOGTAG, "--- center " + x); - } else if (x < 0) { - x = 0; -// Log.d(LOGTAG, "--- zero"); - } else if (x + viewMax > docMax) { - x = docMax - viewMax; -// Log.d(LOGTAG, "--- pin " + x); - } - return x; - } - - // Expects x in view coordinates - private int pinLocX(int x) { - return pinLoc(x, getViewWidth(), computeHorizontalScrollRange()); - } - - // Expects y in view coordinates - private int pinLocY(int y) { - return pinLoc(y, getViewHeight(), computeVerticalScrollRange()); - } - - /*package*/ int viewToContent(int x) { - return Math.round(x * mInvActualScale); - } - - private int contentToView(int x) { - 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) { - - // premature data from webkit, ignore - if ((w | h) == 0) { - return; - } - - // don't abort a scroll animation if we didn't change anything - if (mContentWidth != w || mContentHeight != h) { - // record new dimensions - mContentWidth = w; - mContentHeight = h; - // If history Picture is drawn, don't update scroll. They will be - // updated when we get out of that mode. - if (!mDrawHistory) { - // repin our scroll, taking into account the new content size - int oldX = mScrollX; - int oldY = mScrollY; - mScrollX = pinLocX(mScrollX); - mScrollY = pinLocY(mScrollY); - // android.util.Log.d("skia", "recordNewContentSize - - // abortAnimation"); - mScroller.abortAnimation(); // just in case - if (oldX != mScrollX || oldY != mScrollY) { - sendOurVisibleRect(); - } - } - } - contentSizeChanged(updateLayout); - } - - private void setNewZoomScale(float scale, boolean force) { - if (scale < mMinZoomScale) { - scale = mMinZoomScale; - } else if (scale > mMaxZoomScale) { - scale = mMaxZoomScale; - } - if (scale != mActualScale || force) { - if (mDrawHistory) { - // If history Picture is drawn, don't update scroll. They will - // be updated when we get out of that mode. - if (scale != mActualScale && !mPreviewZoomOnly) { - mCallbackProxy.onScaleChanged(mActualScale, scale); - } - mActualScale = scale; - mInvActualScale = 1 / scale; - if (!mPreviewZoomOnly) { - sendViewSizeZoom(); - } - } else { - // update our scroll so we don't appear to jump - // i.e. keep the center of the doc in the center of the view - - int oldX = mScrollX; - int oldY = mScrollY; - float ratio = scale * mInvActualScale; // old inverse - float sx = ratio * oldX + (ratio - 1) * mZoomCenterX; - float sy = ratio * oldY + (ratio - 1) * mZoomCenterY; - - // now update our new scale and inverse - if (scale != mActualScale && !mPreviewZoomOnly) { - mCallbackProxy.onScaleChanged(mActualScale, scale); - } - mActualScale = scale; - mInvActualScale = 1 / scale; - - // as we don't have animation for scaling, don't do animation - // for scrolling, as it causes weird intermediate state - // pinScrollTo(Math.round(sx), Math.round(sy)); - mScrollX = pinLocX(Math.round(sx)); - mScrollY = pinLocY(Math.round(sy)); - - if (!mPreviewZoomOnly) { - sendViewSizeZoom(); - sendOurVisibleRect(); - } - } - } - } - - // 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_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; - } - - // Sets r to be the visible rectangle of our webview in view coordinates - private void calcOurVisibleRect(Rect r) { - Point p = new Point(); - getGlobalVisibleRect(r, p); - r.offset(-p.x, -p.y); - } - - // Sets r to be our visible rectangle in content coordinates - private void calcOurContentVisibleRect(Rect r) { - calcOurVisibleRect(r); - r.left = viewToContent(r.left); - r.top = viewToContent(r.top); - r.right = viewToContent(r.right); - r.bottom = viewToContent(r.bottom); - } - - /** - * Compute unzoomed width and height, and if they differ from the last - * values we sent, send them to webkit (to be used has new viewport) - * - * @return true if new values were sent - */ - private boolean sendViewSizeZoom() { - int viewWidth = getViewWidth(); - int newWidth = Math.round(viewWidth * mInvActualScale); - int newHeight = Math.round(getViewHeight() * mInvActualScale); - /* - * Because the native side may have already done a layout before the - * View system was able to measure us, we have to send a height of 0 to - * remove excess whitespace when we grow our width. This will trigger a - * layout and a change in content size. This content size change will - * mean that contentSizeChanged will either call this method directly or - * indirectly from onSizeChanged. - */ - if (newWidth > mLastWidthSent && mWrapContent) { - newHeight = 0; - } - // Avoid sending another message if the dimensions have not changed. - if (newWidth != mLastWidthSent || newHeight != mLastHeightSent) { - mWebViewCore.sendMessage(EventHub.VIEW_SIZE_CHANGED, - newWidth, newHeight, new Integer(viewWidth)); - mLastWidthSent = newWidth; - mLastHeightSent = newHeight; - return true; - } - return false; - } - - @Override - protected int computeHorizontalScrollRange() { - if (mDrawHistory) { - return mHistoryWidth; - } else { - return contentToView(mContentWidth); - } - } - - // 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 { - int height = contentToView(mContentHeight); - if (mFindIsUp) { - height += FIND_HEIGHT; - } - return height; - } - } - - /** - * Get the 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. - * @return The url for the current page. - */ - public String getUrl() { - 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 - * until WebViewClient.onReceivedTitle is called. - * @return The title for the current page. - */ - public String getTitle() { - WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem(); - return h != null ? h.getTitle() : null; - } - - /** - * Get the favicon for the current page. This is the favicon of the current - * page until WebViewClient.onReceivedIcon is called. - * @return The favicon for the current page. - */ - public Bitmap getFavicon() { - WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem(); - return h != null ? h.getFavicon() : null; - } - - /** - * Get the progress for the current page. - * @return The progress for the current page between 0 and 100. - */ - public int getProgress() { - return mCallbackProxy.getProgress(); - } - - /** - * @return the height of the HTML content. - */ - public int getContentHeight() { - return mContentHeight; - } - - /** - * Pause all layout, parsing, and javascript timers. This can be useful if - * the WebView is not visible or the application has been paused. - */ - public void pauseTimers() { - mWebViewCore.sendMessage(EventHub.PAUSE_TIMERS); - } - - /** - * Resume all layout, parsing, and javascript timers. This will resume - * dispatching all timers. - */ - public void resumeTimers() { - mWebViewCore.sendMessage(EventHub.RESUME_TIMERS); - } - - /** - * Clear the resource cache. This will cause resources to be re-downloaded - * if accessed again. - * <p> - * Note: this really needs to be a static method as it clears cache for all - * WebView. But we need mWebViewCore to send message to WebCore thread, so - * we can't make this static. - */ - public void clearCache(boolean includeDiskFiles) { - mWebViewCore.sendMessage(EventHub.CLEAR_CACHE, - includeDiskFiles ? 1 : 0, 0); - } - - /** - * Make sure that clearing the form data removes the adapter from the - * currently focused textfield if there is one. - */ - public void clearFormData() { - if (inEditingMode()) { - AutoCompleteAdapter adapter = null; - mTextEntry.setAdapterCustom(adapter); - } - } - - /** - * Tell the WebView to clear its internal back/forward list. - */ - public void clearHistory() { - mCallbackProxy.getBackForwardList().setClearPending(); - mWebViewCore.sendMessage(EventHub.CLEAR_HISTORY); - } - - /** - * Clear the SSL preferences table stored in response to proceeding with SSL - * certificate errors. - */ - public void clearSslPreferences() { - mWebViewCore.sendMessage(EventHub.CLEAR_SSL_PREF_TABLE); - } - - /** - * Return the WebBackForwardList for this WebView. This contains the - * back/forward list for use in querying each item in the history stack. - * This is a copy of the private WebBackForwardList so it contains only a - * snapshot of the current state. Multiple calls to this method may return - * different objects. The object returned from this method will not be - * updated to reflect any new state. - */ - public WebBackForwardList copyBackForwardList() { - return mCallbackProxy.getBackForwardList().clone(); - } - - /* - * Highlight and scroll to the next occurance of String in findAll. - * Wraps the page infinitely, and scrolls. Must be called after - * calling findAll. - * - * @param forward Direction to search. - */ - public void findNext(boolean forward) { - nativeFindNext(forward); - } - - /* - * Find all instances of find on the page and highlight them. - * @param find String to find. - * @return int The number of occurances of the String "find" - * that were found. - */ - 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 - * location. Currently, only addresses in the United States are detected, - * and consist of: - * - a house number - * - a street name - * - a street type (Road, Circle, etc), either spelled out or abbreviated - * - a city name - * - a state or territory, either spelled out or two-letter abbr. - * - an optional 5 digit or 9 digit zip code. - * - * All names must be correctly capitalized, and the zip code, if present, - * must be valid for the state. The street type must be a standard USPS - * spelling or abbreviation. The state or territory must also be spelled - * or abbreviated using USPS standards. The house number may not exceed - * five digits. - * @param addr The string to search for addresses. - * - * @return the address, or if no address is found, return null. - */ - public static String findAddress(String addr) { - return WebViewCore.nativeFindAddress(addr); - } - - /* - * Clear the highlighting surrounding text matches created by findAll. - */ - 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, 0); - invalidate(); - } - - /** - * Query the document to see if it contains any image references. The - * message object will be dispatched with arg1 being set to 1 if images - * were found and 0 if the document does not reference any images. - * @param response The message that will be dispatched with the result. - */ - public void documentHasImages(Message response) { - if (response == null) { - return; - } - mWebViewCore.sendMessage(EventHub.DOC_HAS_IMAGES, response); - } - - @Override - public void computeScroll() { - if (mScroller.computeScrollOffset()) { - int oldX = mScrollX; - int oldY = mScrollY; - mScrollX = mScroller.getCurrX(); - mScrollY = mScroller.getCurrY(); - postInvalidate(); // So we draw again - if (oldX != mScrollX || oldY != mScrollY) { - // as onScrollChanged() is not called, sendOurVisibleRect() - // needs to be call explicitly - sendOurVisibleRect(); - } - } else { - super.computeScroll(); - } - } - - private static int computeDuration(int dx, int dy) { - int distance = Math.max(Math.abs(dx), Math.abs(dy)); - int duration = distance * 1000 / STD_SPEED; - return Math.min(duration, MAX_DURATION); - } - - // helper to pin the scrollBy parameters (already in view coordinates) - // returns true if the scroll was changed - private boolean pinScrollBy(int dx, int dy, boolean animate, int animationDuration) { - return pinScrollTo(mScrollX + dx, mScrollY + dy, animate, animationDuration); - } - - // helper to pin the scrollTo parameters (already in view coordinates) - // returns true if the scroll was changed - private boolean pinScrollTo(int x, int y, boolean animate, int animationDuration) { - x = pinLocX(x); - y = pinLocY(y); - int dx = x - mScrollX; - int dy = y - mScrollY; - - if ((dx | dy) == 0) { - return false; - } - - if (true && animate) { - // Log.d(LOGTAG, "startScroll: " + dx + " " + dy); - - mScroller.startScroll(mScrollX, mScrollY, dx, dy, - animationDuration > 0 ? animationDuration : computeDuration(dx, dy)); - invalidate(); - } else { - mScroller.abortAnimation(); // just in case - scrollTo(x, y); - } - return true; - } - - // Scale from content to view coordinates, and pin. - // Also called by jni webview.cpp - private void setContentScrollBy(int cx, int cy, boolean animate) { - if (mDrawHistory) { - // disallow WebView to change the scroll position as History Picture - // is used in the view system. - // TODO: as we switchOutDrawHistory when trackball or navigation - // keys are hit, this should be safe. Right? - return; - } - cx = contentToView(cx); - cy = contentToView(cy); - if (mHeightCanMeasure) { - // move our visible rect according to scroll request - if (cy != 0) { - Rect tempRect = new Rect(); - calcOurVisibleRect(tempRect); - tempRect.offset(cx, cy); - requestRectangleOnScreen(tempRect); - } - // FIXME: We scroll horizontally no matter what because currently - // ScrollView and ListView will not scroll horizontally. - // FIXME: Why do we only scroll horizontally if there is no - // vertical scroll? -// Log.d(LOGTAG, "setContentScrollBy cy=" + cy); - if (cy == 0 && cx != 0) { - pinScrollBy(cx, 0, animate, 0); - } - } else { - pinScrollBy(cx, cy, animate, 0); - } - } - - // scale from content to view coordinates, and pin - // return true if pin caused the final x/y different than the request cx/cy; - // return false if the view scroll to the exact position as it is requested. - private boolean setContentScrollTo(int cx, int cy) { - if (mDrawHistory) { - // disallow WebView to change the scroll position as History Picture - // is used in the view system. - // One known case where this is called is that WebCore tries to - // restore the scroll position. As history Picture already uses the - // saved scroll position, it is ok to skip this. - return false; - } - int vx = contentToView(cx); - int vy = contentToView(cy); -// Log.d(LOGTAG, "content scrollTo [" + cx + " " + cy + "] view=[" + -// vx + " " + vy + "]"); - pinScrollTo(vx, vy, false, 0); - if (mScrollX != vx || mScrollY != vy) { - return true; - } else { - return false; - } - } - - // scale from content to view coordinates, and pin - private void spawnContentScrollTo(int cx, int cy) { - if (mDrawHistory) { - // disallow WebView to change the scroll position as History Picture - // is used in the view system. - return; - } - int vx = contentToView(cx); - int vy = contentToView(cy); - pinScrollTo(vx, vy, true, 0); - } - - /** - * These are from webkit, and are in content coordinate system (unzoomed) - */ - private void contentSizeChanged(boolean updateLayout) { - // suppress 0,0 since we usually see real dimensions soon after - // this avoids drawing the prev content in a funny place. If we find a - // way to consolidate these notifications, this check may become - // obsolete - if ((mContentWidth | mContentHeight) == 0) { - return; - } - - if (mHeightCanMeasure) { - if (getMeasuredHeight() != contentToView(mContentHeight) - && updateLayout) { - requestLayout(); - } - } else if (mWidthCanMeasure) { - if (getMeasuredWidth() != contentToView(mContentWidth) - && updateLayout) { - requestLayout(); - } - } else { - // If we don't request a layout, try to send our view size to the - // native side to ensure that WebCore has the correct dimensions. - sendViewSizeZoom(); - } - } - - /** - * Set the WebViewClient that will receive various notifications and - * requests. This will replace the current handler. - * @param client An implementation of WebViewClient. - */ - public void setWebViewClient(WebViewClient client) { - mCallbackProxy.setWebViewClient(client); - } - - /** - * Register the interface to be used when content can not be handled by - * the rendering engine, and should be downloaded instead. This will replace - * the current handler. - * @param listener An implementation of DownloadListener. - */ - public void setDownloadListener(DownloadListener listener) { - mCallbackProxy.setDownloadListener(listener); - } - - /** - * Set the chrome handler. This is an implementation of WebChromeClient for - * use in handling Javascript dialogs, favicons, titles, and the progress. - * This will replace the current handler. - * @param client An implementation of WebChromeClient. - */ - public void setWebChromeClient(WebChromeClient client) { - mCallbackProxy.setWebChromeClient(client); - } - - /** - * Set the Picture listener. This is an interface used to receive - * notifications of a new Picture. - * @param listener An implementation of WebView.PictureListener. - */ - public void setPictureListener(PictureListener listener) { - mPictureListener = listener; - } - - /** - * {@hide} - */ - /* FIXME: Debug only! Remove for SDK! */ - public void externalRepresentation(Message callback) { - mWebViewCore.sendMessage(EventHub.REQUEST_EXT_REPRESENTATION, callback); - } - - /** - * {@hide} - */ - /* FIXME: Debug only! Remove for SDK! */ - public void documentAsText(Message callback) { - mWebViewCore.sendMessage(EventHub.REQUEST_DOC_AS_TEXT, callback); - } - - /** - * Use this function to bind an object to Javascript so that the - * methods can be accessed from Javascript. - * IMPORTANT, the object that is bound runs in another thread and - * not in the thread that it was constructed in. - * @param obj The class instance to bind to Javascript - * @param interfaceName The name to used to expose the class in Javascript - */ - public void addJavascriptInterface(Object obj, String interfaceName) { - // Use Hashmap rather than Bundle as Bundles can't cope with Objects - HashMap arg = new HashMap(); - arg.put("object", obj); - arg.put("interfaceName", interfaceName); - mWebViewCore.sendMessage(EventHub.ADD_JS_INTERFACE, arg); - } - - /** - * Return the WebSettings object used to control the settings for this - * WebView. - * @return A WebSettings object that can be used to control this WebView's - * settings. - */ - public WebSettings getSettings() { - return mWebViewCore.getSettings(); - } - - /** - * Return the list of currently loaded plugins. - * @return The list of currently loaded plugins. - */ - public static synchronized PluginList getPluginList() { - if (sPluginList == null) { - sPluginList = new PluginList(); - } - return sPluginList; - } - - /** - * Signal the WebCore thread to refresh its list of plugins. Use - * this if the directory contents of one of the plugin directories - * has been modified and needs its changes reflecting. May cause - * plugin load and/or unload. - * @param reloadOpenPages Set to true to reload all open pages. - */ - public void refreshPlugins(boolean reloadOpenPages) { - if (mWebViewCore != null) { - mWebViewCore.sendMessage(EventHub.REFRESH_PLUGINS, reloadOpenPages); - } - } - - //------------------------------------------------------------------------- - // Override View methods - //------------------------------------------------------------------------- - - @Override - protected void finalize() throws Throwable { - destroy(); - } - - @Override - protected void onDraw(Canvas canvas) { - // if mNativeClass is 0, the WebView has been destroyed. Do nothing. - if (mNativeClass == 0) { - return; - } - if (mWebViewCore.mEndScaleZoom) { - mWebViewCore.mEndScaleZoom = false; - if (mTouchMode >= FIRST_SCROLL_ZOOM - && mTouchMode <= LAST_SCROLL_ZOOM) { - setHorizontalScrollBarEnabled(true); - setVerticalScrollBarEnabled(true); - mTouchMode = TOUCH_DONE_MODE; - } - } - int sc = canvas.save(); - if (mTouchMode >= FIRST_SCROLL_ZOOM && mTouchMode <= LAST_SCROLL_ZOOM) { - scrollZoomDraw(canvas); - } else { - nativeRecomputeFocus(); - // Update the buttons in the picture, so when we draw the picture - // to the screen, they are in the correct state. - // 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(hasFocus() && hasWindowFocus(), - mTouchMode == TOUCH_SHORTPRESS_START_MODE - || mTrackballDown || mGotEnterDown, false); - drawCoreAndFocusRing(canvas, mBackgroundColor, mDrawFocusRing); - } - canvas.restoreToCount(sc); - - if (AUTO_REDRAW_HACK && mAutoRedraw) { - invalidate(); - } - } - - @Override - public void setLayoutParams(ViewGroup.LayoutParams params) { - if (params.height == LayoutParams.WRAP_CONTENT) { - mWrapContent = true; - } - super.setLayoutParams(params); - } - - @Override - public boolean performLongClick() { - if (inEditingMode()) { - return mTextEntry.performLongClick(); - } else { - return super.performLongClick(); - } - } - - private void drawCoreAndFocusRing(Canvas canvas, int color, - boolean drawFocus) { - if (mDrawHistory) { - canvas.scale(mActualScale, mActualScale); - canvas.drawPicture(mHistoryPicture); - return; - } - - boolean animateZoom = mZoomScale != 0; - boolean animateScroll = !mScroller.isFinished() - || mVelocityTracker != null; - if (animateZoom) { - float zoomScale; - int interval = (int) (SystemClock.uptimeMillis() - mZoomStart); - if (interval < ZOOM_ANIMATION_LENGTH) { - float ratio = (float) interval / ZOOM_ANIMATION_LENGTH; - zoomScale = 1.0f / (mInvInitialZoomScale - + (mInvFinalZoomScale - mInvInitialZoomScale) * ratio); - invalidate(); - } else { - zoomScale = mZoomScale; - // set mZoomScale to be 0 as we have done animation - mZoomScale = 0; - } - float scale = (mActualScale - zoomScale) * mInvActualScale; - float tx = scale * (mZoomCenterX + mScrollX); - float ty = scale * (mZoomCenterY + mScrollY); - - // this block pins the translate to "legal" bounds. This makes the - // animation a bit non-obvious, but it means we won't pop when the - // "real" zoom takes effect - if (true) { - // canvas.translate(mScrollX, mScrollY); - tx -= mScrollX; - ty -= mScrollY; - tx = -pinLoc(-Math.round(tx), getViewWidth(), Math - .round(mContentWidth * zoomScale)); - ty = -pinLoc(-Math.round(ty), getViewHeight(), Math - .round(mContentHeight * zoomScale)); - tx += mScrollX; - ty += mScrollY; - } - canvas.translate(tx, ty); - canvas.scale(zoomScale, zoomScale); - } else { - canvas.scale(mActualScale, mActualScale); - } - - mWebViewCore.drawContentPicture(canvas, color, animateZoom, - animateScroll); - - if (mNativeClass == 0) return; - if (mShiftIsPressed) { - if (mTouchSelection) { - nativeDrawSelectionRegion(canvas); - } else { - nativeDrawSelection(canvas, mSelectX, mSelectY, - mExtendSelection); - } - } else if (drawFocus) { - if (mTouchMode == TOUCH_SHORTPRESS_START_MODE) { - mTouchMode = TOUCH_SHORTPRESS_MODE; - HitTestResult hitTest = getHitTestResult(); - if (hitTest != null && - hitTest.mType != HitTestResult.UNKNOWN_TYPE) { - mPrivateHandler.sendMessageDelayed(mPrivateHandler - .obtainMessage(SWITCH_TO_LONGPRESS), - LONG_PRESS_TIMEOUT); - } - } - 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) - / (float) SCROLL_ZOOM_GRID; - return 1.0f / griddedInvScale; - } - - private float scrollZoomX(float scale) { - int width = getViewWidth(); - float maxScrollZoomX = mContentWidth * scale - width; - int maxX = mContentWidth - width; - return -(maxScrollZoomX > 0 ? mZoomScrollX * maxScrollZoomX / maxX - : maxScrollZoomX / 2); - } - - private float scrollZoomY(float scale) { - int height = getViewHeight(); - float maxScrollZoomY = mContentHeight * scale - height; - int maxY = mContentHeight - height; - return -(maxScrollZoomY > 0 ? mZoomScrollY * maxScrollZoomY / maxY - : maxScrollZoomY / 2); - } - - private void drawMagnifyFrame(Canvas canvas, Rect frame, Paint paint) { - final float ADORNMENT_LEN = 16.0f; - float width = frame.width(); - float height = frame.height(); - Path path = new Path(); - path.moveTo(-ADORNMENT_LEN, -ADORNMENT_LEN); - path.lineTo(0, 0); - path.lineTo(width, 0); - path.lineTo(width + ADORNMENT_LEN, -ADORNMENT_LEN); - path.moveTo(-ADORNMENT_LEN, height + ADORNMENT_LEN); - path.lineTo(0, height); - path.lineTo(width, height); - path.lineTo(width + ADORNMENT_LEN, height + ADORNMENT_LEN); - path.moveTo(0, 0); - path.lineTo(0, height); - path.moveTo(width, 0); - path.lineTo(width, height); - path.offset(frame.left, frame.top); - canvas.drawPath(path, paint); - } - - // Returns frame surrounding magified portion of screen while - // scroll-zoom is enabled. The frame is also used to center the - // zoom-in zoom-out points at the start and end of the animation. - private Rect scrollZoomFrame(int width, int height, float halfScale) { - Rect scrollFrame = new Rect(); - scrollFrame.set(mZoomScrollX, mZoomScrollY, - mZoomScrollX + width, mZoomScrollY + height); - 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 * 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; - } - - private void scrollZoomDraw(Canvas canvas) { - float invScale = mZoomScrollInvLimit; - int elapsed = 0; - if (mTouchMode != SCROLL_ZOOM_OUT) { - elapsed = (int) Math.min(System.currentTimeMillis() - - mZoomScrollStart, SCROLL_ZOOM_DURATION); - float transitionScale = (mZoomScrollInvLimit - mInvActualScale) - * elapsed / SCROLL_ZOOM_DURATION; - if (mTouchMode == SCROLL_ZOOM_ANIMATION_OUT) { - invScale = mInvActualScale + transitionScale; - } else { /* if (mTouchMode == SCROLL_ZOOM_ANIMATION_IN) */ - invScale = mZoomScrollInvLimit - transitionScale; - } - } - float scale = scrollZoomGridScale(invScale); - invScale = 1.0f / scale; - int width = getViewWidth(); - int height = getViewHeight(); - float halfScale = scrollZoomMagScale(invScale); - Rect scrollFrame = scrollZoomFrame(width, height, halfScale); - if (elapsed == SCROLL_ZOOM_DURATION) { - if (mTouchMode == SCROLL_ZOOM_ANIMATION_IN) { - setHorizontalScrollBarEnabled(true); - setVerticalScrollBarEnabled(true); - updateTextEntry(); - scrollTo((int) (scrollFrame.centerX() * mActualScale) - - (width >> 1), (int) (scrollFrame.centerY() - * mActualScale) - (height >> 1)); - mTouchMode = TOUCH_DONE_MODE; - } else { - mTouchMode = SCROLL_ZOOM_OUT; - } - } - float newX = scrollZoomX(scale); - float newY = scrollZoomY(scale); - if (LOGV_ENABLED) { - Log.v(LOGTAG, "scrollZoomDraw scale=" + scale + " + (" + newX - + ", " + newY + ") mZoomScroll=(" + mZoomScrollX + ", " - + mZoomScrollY + ")" + " invScale=" + invScale + " scale=" - + scale); - } - canvas.translate(newX, newY); - canvas.scale(scale, scale); - boolean animating = mTouchMode != SCROLL_ZOOM_OUT; - if (mDrawHistory) { - int sc = canvas.save(Canvas.CLIP_SAVE_FLAG); - Rect clip = new Rect(0, 0, mHistoryPicture.getWidth(), - mHistoryPicture.getHeight()); - canvas.clipRect(clip, Region.Op.DIFFERENCE); - canvas.drawColor(mBackgroundColor); - canvas.restoreToCount(sc); - canvas.drawPicture(mHistoryPicture); - } else { - mWebViewCore.drawContentPicture(canvas, mBackgroundColor, - animating, true); - } - if (mTouchMode == TOUCH_DONE_MODE) { - return; - } - Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); - paint.setStyle(Paint.Style.STROKE); - paint.setStrokeWidth(30.0f); - paint.setARGB(0x50, 0, 0, 0); - int maxX = mContentWidth - width; - int maxY = mContentHeight - height; - if (true) { // experiment: draw hint to place finger off magnify area - drawMagnifyFrame(canvas, scrollFrame, paint); - } else { - canvas.drawRect(scrollFrame, paint); - } - int sc = canvas.save(); - canvas.clipRect(scrollFrame); - 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) { - Log.v(LOGTAG, "scrollZoomDraw halfScale=" + halfScale + " w/h=(" - + width + ", " + height + ") half=(" + halfX + ", " - + halfY + ")"); - } - if (mDrawHistory) { - canvas.drawPicture(mHistoryPicture); - } else { - mWebViewCore.drawContentPicture(canvas, mBackgroundColor, - animating, false); - } - canvas.restoreToCount(sc); - if (mTouchMode != SCROLL_ZOOM_OUT) { - invalidate(); - } - } - - private void zoomScrollTap(float x, float y) { - float scale = scrollZoomGridScale(mZoomScrollInvLimit); - float left = scrollZoomX(scale); - float top = scrollZoomY(scale); - int width = getViewWidth(); - int height = getViewHeight(); - x -= width * scale / 2; - y -= height * scale / 2; - mZoomScrollX = Math.min(mContentWidth - width - , Math.max(0, (int) ((x - left) / scale))); - mZoomScrollY = Math.min(mContentHeight - height - , Math.max(0, (int) ((y - top) / scale))); - if (LOGV_ENABLED) { - Log.v(LOGTAG, "zoomScrollTap scale=" + scale + " + (" + left - + ", " + top + ") mZoomScroll=(" + mZoomScrollX + ", " - + mZoomScrollY + ")" + " x=" + x + " y=" + y); - } - } - - private boolean canZoomScrollOut() { - if (mContentWidth == 0 || mContentHeight == 0) { - return false; - } - int width = getViewWidth(); - int height = getViewHeight(); - float x = (float) width / (float) mContentWidth; - float y = (float) height / (float) mContentHeight; - mZoomScrollLimit = Math.max(DEFAULT_MIN_ZOOM_SCALE, Math.min(x, y)); - mZoomScrollInvLimit = 1.0f / mZoomScrollLimit; - if (LOGV_ENABLED) { - Log.v(LOGTAG, "canZoomScrollOut" - + " mInvActualScale=" + mInvActualScale - + " mZoomScrollLimit=" + mZoomScrollLimit - + " mZoomScrollInvLimit=" + mZoomScrollInvLimit - + " mContentWidth=" + mContentWidth - + " mContentHeight=" + mContentHeight - ); - } - // don't zoom out unless magnify area is at least half as wide - // or tall as content - float limit = mZoomScrollLimit * 2; - return mContentWidth >= width * limit - || mContentHeight >= height * limit; - } - - private void startZoomScrollOut() { - setHorizontalScrollBarEnabled(false); - setVerticalScrollBarEnabled(false); - if (mZoomControlRunnable != null) { - mPrivateHandler.removeCallbacks(mZoomControlRunnable); - } - if (mZoomControls != null) { - mZoomControls.hide(); - } - int width = getViewWidth(); - int height = getViewHeight(); - int halfW = width >> 1; - mLastTouchX = halfW; - int halfH = height >> 1; - mLastTouchY = halfH; - mScroller.abortAnimation(); - mZoomScrollStart = System.currentTimeMillis(); - Rect zoomFrame = scrollZoomFrame(width, height - , scrollZoomMagScale(mZoomScrollInvLimit)); - mZoomScrollX = Math.max(0, (int) ((mScrollX + halfW) * mInvActualScale) - - (zoomFrame.width() >> 1)); - mZoomScrollY = Math.max(0, (int) ((mScrollY + halfH) * mInvActualScale) - - (zoomFrame.height() >> 1)); - scrollTo(0, 0); // triggers inval, starts animation - clearTextEntry(); - if (LOGV_ENABLED) { - Log.v(LOGTAG, "startZoomScrollOut mZoomScroll=(" - + mZoomScrollX + ", " + mZoomScrollY +")"); - } - } - - private void zoomScrollOut() { - if (canZoomScrollOut() == false) { - mTouchMode = TOUCH_DONE_MODE; - return; - } - startZoomScrollOut(); - mTouchMode = SCROLL_ZOOM_ANIMATION_OUT; - invalidate(); - } - - private void moveZoomScrollWindow(float x, float y) { - if (Math.abs(x - mLastZoomScrollRawX) < 1.5f - && Math.abs(y - mLastZoomScrollRawY) < 1.5f) { - return; - } - mLastZoomScrollRawX = x; - mLastZoomScrollRawY = y; - int oldX = mZoomScrollX; - int oldY = mZoomScrollY; - int width = getViewWidth(); - int height = getViewHeight(); - int maxZoomX = mContentWidth - width; - if (maxZoomX > 0) { - int maxScreenX = width - (int) Math.ceil(width - * mZoomScrollLimit) - SCROLL_ZOOM_FINGER_BUFFER; - if (LOGV_ENABLED) { - Log.v(LOGTAG, "moveZoomScrollWindow-X" - + " maxScreenX=" + maxScreenX + " width=" + width - + " mZoomScrollLimit=" + mZoomScrollLimit + " x=" + x); - } - x += maxScreenX * mLastScrollX / maxZoomX - mLastTouchX; - x *= Math.max(maxZoomX / maxScreenX, mZoomScrollInvLimit); - mZoomScrollX = Math.max(0, Math.min(maxZoomX, (int) x)); - } - int maxZoomY = mContentHeight - height; - if (maxZoomY > 0) { - int maxScreenY = height - (int) Math.ceil(height - * mZoomScrollLimit) - SCROLL_ZOOM_FINGER_BUFFER; - if (LOGV_ENABLED) { - Log.v(LOGTAG, "moveZoomScrollWindow-Y" - + " maxScreenY=" + maxScreenY + " height=" + height - + " mZoomScrollLimit=" + mZoomScrollLimit + " y=" + y); - } - y += maxScreenY * mLastScrollY / maxZoomY - mLastTouchY; - y *= Math.max(maxZoomY / maxScreenY, mZoomScrollInvLimit); - mZoomScrollY = Math.max(0, Math.min(maxZoomY, (int) y)); - } - if (oldX != mZoomScrollX || oldY != mZoomScrollY) { - invalidate(); - } - if (LOGV_ENABLED) { - Log.v(LOGTAG, "moveZoomScrollWindow" - + " scrollTo=(" + mZoomScrollX + ", " + mZoomScrollY + ")" - + " mLastTouch=(" + mLastTouchX + ", " + mLastTouchY + ")" - + " maxZoom=(" + maxZoomX + ", " + maxZoomY + ")" - + " last=("+mLastScrollX+", "+mLastScrollY+")" - + " x=" + x + " y=" + y); - } - } - - private void setZoomScrollIn() { - mZoomScrollStart = System.currentTimeMillis(); - } - - private float mZoomScrollLimit; - private float mZoomScrollInvLimit; - private int mLastScrollX; - private int mLastScrollY; - private long mZoomScrollStart; - private int mZoomScrollX; - private int mZoomScrollY; - private float mLastZoomScrollRawX = -1000.0f; - private float mLastZoomScrollRawY = -1000.0f; - // The zoomed scale varies from 1.0 to DEFAULT_MIN_ZOOM_SCALE == 0.25. - // The zoom animation duration SCROLL_ZOOM_DURATION == 0.5. - // Two pressures compete for gridding; a high frame rate (e.g. 20 fps) - // and minimizing font cache allocations (fewer frames is better). - // A SCROLL_ZOOM_GRID of 6 permits about 20 zoom levels over 0.5 seconds: - // the inverse of: 1.0, 1.16, 1.33, 1.5, 1.67, 1.84, 2.0, etc. to 4.0 - private static final int SCROLL_ZOOM_GRID = 6; - private static final int SCROLL_ZOOM_DURATION = 500; - // Make it easier to get to the bottom of a document by reserving a 32 - // pixel buffer, for when the starting drag is a bit below the bottom of - // the magnify frame. - private static final int SCROLL_ZOOM_FINGER_BUFFER = 32; - - // draw history - private boolean mDrawHistory = false; - private Picture mHistoryPicture = null; - private int mHistoryWidth = 0; - private int mHistoryHeight = 0; - - // Only check the flag, can be called from WebCore thread - boolean drawHistory() { - return mDrawHistory; - } - - // Should only be called in UI thread - void switchOutDrawHistory() { - if (null == mWebViewCore) return; // CallbackProxy may trigger this - if (mDrawHistory) { - mDrawHistory = false; - invalidate(); - int oldScrollX = mScrollX; - int oldScrollY = mScrollY; - mScrollX = pinLocX(mScrollX); - mScrollY = pinLocY(mScrollY); - if (oldScrollX != mScrollX || oldScrollY != mScrollY) { - mUserScroll = false; - mWebViewCore.sendMessage(EventHub.SYNC_SCROLL, oldScrollX, - oldScrollY); - } - sendOurVisibleRect(); - } - } - - /** - * Class representing the node which is focused. - */ - private class FocusNode { - public FocusNode() { - mBounds = new Rect(); - } - // Only to be called by JNI - private void setAll(boolean isTextField, boolean isTextArea, boolean - isPassword, boolean isAnchor, boolean isRtlText, int maxLength, - int textSize, int boundsX, int boundsY, int boundsRight, int - boundsBottom, int nodePointer, int framePointer, String text, - String name, int rootTextGeneration) { - mIsTextField = isTextField; - mIsTextArea = isTextArea; - mIsPassword = isPassword; - mIsAnchor = isAnchor; - mIsRtlText = isRtlText; - - mMaxLength = maxLength; - mTextSize = textSize; - - mBounds.set(boundsX, boundsY, boundsRight, boundsBottom); - - - mNodePointer = nodePointer; - mFramePointer = framePointer; - mText = text; - mName = name; - mRootTextGeneration = rootTextGeneration; - } - public boolean mIsTextField; - public boolean mIsTextArea; - public boolean mIsPassword; - public boolean mIsAnchor; - public boolean mIsRtlText; - - public int mSelectionStart; - public int mSelectionEnd; - public int mMaxLength; - public int mTextSize; - - public Rect mBounds; - - public int mNodePointer; - public int mFramePointer; - public String mText; - public String mName; - public int mRootTextGeneration; - } - - // Warning: ONLY use mFocusNode AFTER calling nativeUpdateFocusNode(), - // and ONLY if it returns true; - private FocusNode mFocusNode = new FocusNode(); - - /** - * Delete text from start to end in the focused textfield. If there is no - * focus, or if start == end, silently fail. If start and end are out of - * order, swap them. - * @param start Beginning of selection to delete. - * @param end End of selection to delete. - */ - /* package */ void deleteSelection(int start, int end) { - mWebViewCore.sendMessage(EventHub.DELETE_SELECTION, start, end, - new WebViewCore.FocusData(mFocusData)); - } - - /** - * Set the selection to (start, end) in the focused textfield. If start and - * end are out of order, swap them. - * @param start Beginning of selection. - * @param end End of selection. - */ - /* package */ void setSelection(int start, int end) { - mWebViewCore.sendMessage(EventHub.SET_SELECTION, start, end, - 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, 0); - mTextEntry.enableScrollOnScreen(true); - // Now we need to fake a touch event to place the cursor where the - // user touched. - AbsoluteLayout.LayoutParams lp = (AbsoluteLayout.LayoutParams) - mTextEntry.getLayoutParams(); - if (lp != null) { - // Take the last touch and adjust for the location of the - // TextDialog. - float x = mLastTouchX - lp.x; - float y = mLastTouchY - lp.y; - mTextEntry.fakeTouchEvent(x, y); - } - } - - private void updateTextEntry() { - if (mTextEntry == null) { - mTextEntry = new TextDialog(mContext, WebView.this); - // Initialize our generation number. - mTextGeneration = 0; - } - // If we do not have focus, do nothing until we gain focus. - if (!hasFocus() && !mTextEntry.hasFocus() - || (mTouchMode >= FIRST_SCROLL_ZOOM - && mTouchMode <= LAST_SCROLL_ZOOM)) { - mNeedsUpdateTextEntry = true; - return; - } - boolean alreadyThere = inEditingMode(); - if (0 == mNativeClass || !nativeUpdateFocusNode()) { - if (alreadyThere) { - mTextEntry.remove(); - } - return; - } - FocusNode node = mFocusNode; - if (!node.mIsTextField && !node.mIsTextArea) { - if (alreadyThere) { - mTextEntry.remove(); - } - return; - } - mTextEntry.setTextSize(contentToView(node.mTextSize)); - Rect visibleRect = sendOurVisibleRect(); - // Note that sendOurVisibleRect calls viewToContent, so the coordinates - // should be in content coordinates. - if (!Rect.intersects(node.mBounds, visibleRect)) { - // Node is not on screen, so do not bother. - return; - } - int x = node.mBounds.left; - int y = node.mBounds.top; - int width = node.mBounds.width(); - int height = node.mBounds.height(); - if (alreadyThere && mTextEntry.isSameTextField(node.mNodePointer)) { - // It is possible that we have the same textfield, but it has moved, - // i.e. In the case of opening/closing the screen. - // In that case, we need to set the dimensions, but not the other - // aspects. - // We also need to restore the selection, which gets wrecked by - // calling setTextEntryRect. - Spannable spannable = (Spannable) mTextEntry.getText(); - int start = Selection.getSelectionStart(spannable); - int end = Selection.getSelectionEnd(spannable); - setTextEntryRect(x, y, width, height); - // If the text has been changed by webkit, update it. However, if - // there has been more UI text input, ignore it. We will receive - // another update when that text is recognized. - if (node.mText != null && !node.mText.equals(spannable.toString()) - && node.mRootTextGeneration == mTextGeneration) { - mTextEntry.setTextAndKeepSelection(node.mText); - } else { - Selection.setSelection(spannable, start, end); - } - } else { - String text = node.mText; - setTextEntryRect(x, y, width, height); - mTextEntry.setGravity(node.mIsRtlText ? Gravity.RIGHT : - Gravity.NO_GRAVITY); - // this needs to be called before update adapter thread starts to - // ensure the mTextEntry has the same node pointer - mTextEntry.setNodePointer(node.mNodePointer); - int maxLength = -1; - if (node.mIsTextField) { - maxLength = node.mMaxLength; - if (mWebViewCore.getSettings().getSaveFormData() - && node.mName != null) { - HashMap data = new HashMap(); - data.put("text", node.mText); - Message update = mPrivateHandler.obtainMessage( - UPDATE_TEXT_ENTRY_ADAPTER, node.mNodePointer, 0, - data); - UpdateTextEntryAdapter updater = new UpdateTextEntryAdapter( - node.mName, getUrl(), update); - Thread t = new Thread(updater); - t.start(); - } - } - mTextEntry.setMaxLength(maxLength); - AutoCompleteAdapter adapter = null; - mTextEntry.setAdapterCustom(adapter); - mTextEntry.setSingleLine(node.mIsTextField); - mTextEntry.setInPassword(node.mIsPassword); - if (null == text) { - mTextEntry.setText("", 0, 0); - } else { - // Change to true to enable the old style behavior, where - // entering a textfield/textarea always set the selection to the - // whole field. This was desirable for the case where the user - // intends to scroll past the field using the trackball. - // However, it causes a problem when replying to emails - the - // user expects the cursor to be at the beginning of the - // textarea. Testing out a new behavior, where textfields set - // selection at the end, and textareas at the beginning. - if (false) { - mTextEntry.setText(text, 0, text.length()); - } else if (node.mIsTextField) { - int length = text.length(); - mTextEntry.setText(text, length, length); - } else { - mTextEntry.setText(text, 0, 0); - } - } - mTextEntry.requestFocus(); - } - } - - private class UpdateTextEntryAdapter implements Runnable { - private String mName; - private String mUrl; - private Message mUpdateMessage; - - public UpdateTextEntryAdapter(String name, String url, Message msg) { - mName = name; - mUrl = url; - mUpdateMessage = msg; - } - - public void run() { - ArrayList<String> pastEntries = mDatabase.getFormData(mUrl, mName); - if (pastEntries.size() > 0) { - AutoCompleteAdapter adapter = new - AutoCompleteAdapter(mContext, pastEntries); - ((HashMap) mUpdateMessage.obj).put("adapter", adapter); - mUpdateMessage.sendToTarget(); - } - } - } - - private void setTextEntryRect(int x, int y, int width, int height) { - x = contentToView(x); - y = contentToView(y); - width = contentToView(width); - height = contentToView(height); - mTextEntry.setRect(x, y, width, height); - } - - // 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 boolean mGotEnterDown = false; - - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { - if (LOGV_ENABLED) { - Log.v(LOGTAG, "keyDown at " + System.currentTimeMillis() - + ", " + event); - } - - if (mNativeClass == 0) { - return 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; - } - - // 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 (mShiftIsPressed == false && nativeFocusNodeWantsKeyEvents() == false - && (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT - || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT)) { - mExtendSelection = false; - mShiftIsPressed = true; - if (nativeUpdateFocusNode()) { - FocusNode node = mFocusNode; - mSelectX = contentToView(node.mBounds.left); - mSelectY = contentToView(node.mBounds.top); - } else { - mSelectX = mScrollX + (int) mLastTouchX; - mSelectY = mScrollY + (int) mLastTouchY; - } - } - - 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 (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); - // Already checked mNativeClass, so we do not need to check it - // again. - nativeRecordButtons(hasFocus() && hasWindowFocus(), true, true); - return true; - } - // Bubble up the key event as WebView doesn't handle it - return false; - } - - if (getSettings().getNavDump()) { - switch (keyCode) { - case KeyEvent.KEYCODE_4: - // "/data/data/com.android.browser/displayTree.txt" - nativeDumpDisplayTree(getUrl()); - break; - 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_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; - case KeyEvent.KEYCODE_9: - nativeInstrumentReport(); - return true; - } - } - - // TODO: should we pass all the keys to DOM or check the meta tag - if (nativeFocusNodeWantsKeyEvents() || true) { - // pass the key to DOM - mWebViewCore.sendMessage(EventHub.KEY_DOWN, event); - // return true as DOM handles the key - return true; - } - - // Bubble up the key event as WebView doesn't handle it - return false; - } - - @Override - public boolean onKeyUp(int keyCode, KeyEvent event) { - if (LOGV_ENABLED) { - Log.v(LOGTAG, "keyUp at " + System.currentTimeMillis() - + ", " + event); - } - - 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) { - setZoomScrollIn(); - mTouchMode = SCROLL_ZOOM_ANIMATION_IN; - invalidate(); - return true; - } - return false; - } - - if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT - || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) { - if (commitCopy()) { - return true; - } - } - - 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. - if (nativeUpdateFocusNode()) { - if (Rect.intersects(mFocusNode.mBounds, visibleRect)) { - nativeSetFollowedLink(true); - mWebViewCore.sendMessage(EventHub.SET_FINAL_FOCUS, - EventHub.BLOCK_FOCUS_CHANGE_UNTIL_KEY_UP, 0, - new WebViewCore.FocusData(mFocusData)); - playSoundEffect(SoundEffectConstants.CLICK); - if (!mCallbackProxy.uiOverrideUrlLoading(mFocusNode.mText)) { - // use CLICK instead of KEY_DOWN/KEY_UP so that we can - // trigger mouse click events - mWebViewCore.sendMessage(EventHub.CLICK); - } - } - return true; - } - // Bubble up the key event as WebView doesn't handle it - return false; - } - - // TODO: should we pass all the keys to DOM or check the meta tag - if (nativeFocusNodeWantsKeyEvents() || true) { - // pass the key to DOM - mWebViewCore.sendMessage(EventHub.KEY_UP, event); - // return true as DOM handles the key - return true; - } - - // Bubble up the key event as WebView doesn't handle it - return false; - } - - /** - * @hide - */ - public void emulateShiftHeld() { - mExtendSelection = false; - mShiftIsPressed = true; - } - - 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; - } - mExtendSelection = false; - } - 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 - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - ViewParent parent = getParent(); - if (parent instanceof ViewGroup) { - ViewGroup p = (ViewGroup) parent; - p.setOnHierarchyChangeListener(this); - } - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - ViewParent parent = getParent(); - if (parent instanceof ViewGroup) { - ViewGroup p = (ViewGroup) parent; - p.setOnHierarchyChangeListener(null); - } - - // Clean up the zoom ring - mZoomRingController.setVisible(false); - } - - // Implementation for OnHierarchyChangeListener - public void onChildViewAdded(View parent, View child) {} - - public void onChildViewRemoved(View p, View child) { - if (child == this) { - if (inEditingMode()) { - clearTextEntry(); - mNeedsUpdateTextEntry = true; - } - } - } - - /** - * @deprecated WebView should not have implemented - * ViewTreeObserver.OnGlobalFocusChangeListener. This method - * does nothing now. - */ - @Deprecated - public void onGlobalFocusChanged(View oldFocus, View newFocus) { - } - - // To avoid drawing the focus ring, and remove the TextView when our window - // loses focus. - @Override - public void onWindowFocusChanged(boolean hasWindowFocus) { - if (hasWindowFocus) { - if (hasFocus()) { - // If our window regained focus, and we have focus, then begin - // drawing the focus ring, and restore the TextView if - // necessary. - mDrawFocusRing = true; - if (mNeedsUpdateTextEntry) { - updateTextEntry(); - } - if (mNativeClass != 0) { - nativeRecordButtons(true, false, true); - } - } else { - // If our window gained focus, but we do not have it, do not - // draw the focus ring. - mDrawFocusRing = false; - // We do not call nativeRecordButtons here because we assume - // that when we lost focus, or window focus, it got called with - // false for the first parameter - } - } else { - // If our window has lost focus, stop drawing the focus ring - mDrawFocusRing = false; - mGotKeyDown = false; - mShiftIsPressed = false; - if (mNativeClass != 0) { - nativeRecordButtons(false, false, true); - } - } - invalidate(); - super.onWindowFocusChanged(hasWindowFocus); - } - - @Override - protected void onFocusChanged(boolean focused, int direction, - Rect previouslyFocusedRect) { - if (LOGV_ENABLED) { - Log.v(LOGTAG, "MT focusChanged " + focused + ", " + direction); - } - if (focused) { - // When we regain focus, if we have window focus, resume drawing - // the focus ring, and add the TextView if necessary. - if (hasWindowFocus()) { - mDrawFocusRing = true; - if (mNeedsUpdateTextEntry) { - updateTextEntry(); - mNeedsUpdateTextEntry = false; - } - if (mNativeClass != 0) { - nativeRecordButtons(true, false, true); - } - //} else { - // The WebView has gained focus while we do not have - // windowfocus. When our window lost focus, we should have - // called nativeRecordButtons(false...) - } - } else { - // When we lost focus, unless focus went to the TextView (which is - // true if we are in editing mode), stop drawing the focus ring. - if (!inEditingMode()) { - mDrawFocusRing = false; - if (mNativeClass != 0) { - nativeRecordButtons(false, false, true); - } - } - mGotKeyDown = false; - } - - super.onFocusChanged(focused, direction, previouslyFocusedRect); - } - - @Override - protected void onSizeChanged(int w, int h, int ow, int oh) { - super.onSizeChanged(w, h, ow, oh); - // Center zooming to the center of the screen. This is appropriate for - // this case of zooming, and it also sets us up properly if we remove - // the new zoom ring controller - mZoomCenterX = getViewWidth() * .5f; - mZoomCenterY = getViewHeight() * .5f; - - // update mMinZoomScale - if (mMinContentWidth > MAX_FLOAT_CONTENT_WIDTH) { - boolean atMin = Math.abs(mActualScale - mMinZoomScale) < 0.01f; - mMinZoomScale = (float) getViewWidth() / mContentWidth; - if (atMin) { - // if the WebView was at the minimum zoom scale, keep it. e,g., - // the WebView was at the minimum zoom scale at the portrait - // mode, rotate it to the landscape modifying the scale to the - // new minimum zoom scale, when rotating back, we would like to - // keep the minimum zoom scale instead of keeping the same scale - // as normally we do. - mActualScale = mMinZoomScale; - } - } - - // we always force, in case our height changed, in which case we still - // want to send the notification over to webkit - setNewZoomScale(mActualScale, true); - } - - @Override - protected void onScrollChanged(int l, int t, int oldl, int oldt) { - super.onScrollChanged(l, t, oldl, oldt); - sendOurVisibleRect(); - } - - - @Override - public boolean dispatchKeyEvent(KeyEvent event) { - boolean dispatch = true; - - if (!inEditingMode()) { - if (event.getAction() == KeyEvent.ACTION_DOWN) { - mGotKeyDown = true; - } else { - if (!mGotKeyDown) { - /* - * We got a key up for which we were not the recipient of - * the original key down. Don't give it to the view. - */ - dispatch = false; - } - mGotKeyDown = false; - } - } - - if (dispatch) { - return super.dispatchKeyEvent(event); - } else { - // We didn't dispatch, so let something else handle the key - return false; - } - } - - // Here are the snap align logic: - // 1. If it starts nearly horizontally or vertically, snap align; - // 2. If there is a dramitic direction change, let it go; - // 3. If there is a same direction back and forth, lock it. - - // adjustable parameters - private int mMinLockSnapReverseDistance; - private static final float MAX_SLOPE_FOR_DIAG = 1.5f; - private static final int MIN_BREAK_SNAP_CROSS_DISTANCE = 80; - - @Override - public boolean onTouchEvent(MotionEvent ev) { - if (mNativeClass == 0 || !isClickable() || !isLongClickable()) { - return false; - } - - if (mShowZoomRingTutorial && getSettings().supportZoom() - && (mMaxZoomScale - mMinZoomScale) > ZOOM_RING_STEPS * 0.01f) { - ZoomRingController.showZoomTutorialOnce(mContext); - mShowZoomRingTutorial = false; - mPrivateHandler.sendMessageDelayed(mPrivateHandler - .obtainMessage(DISMISS_ZOOM_RING_TUTORIAL), - ZOOM_RING_TUTORIAL_DURATION); - } - - if (LOGV_ENABLED) { - Log.v(LOGTAG, ev + " at " + ev.getEventTime() + " mTouchMode=" - + mTouchMode); - } - - if (mZoomRingController.isVisible() && mInZoomTapDragMode) { - if (ev.getAction() == MotionEvent.ACTION_UP) { - // Just released the second tap, no longer in tap-drag mode - mInZoomTapDragMode = false; - } - return mZoomRingController.handleDoubleTapEvent(ev); - } - - int action = ev.getAction(); - 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 - || mTouchMode == SCROLL_ZOOM_ANIMATION_OUT) { - // no interaction while animation is in progress - break; - } else if (mTouchMode == SCROLL_ZOOM_OUT) { - mLastScrollX = mZoomScrollX; - mLastScrollY = mZoomScrollY; - // If two taps are close, ignore the first tap - } else if (!mScroller.isFinished()) { - 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 if (!ZoomRingController.useOldZoom(mContext) && - mPrivateHandler.hasMessages(RELEASE_SINGLE_TAP)) { - // Found doubletap, invoke the zoom controller - mPrivateHandler.removeMessages(RELEASE_SINGLE_TAP); - mZoomRingController.setVisible(true); - mInZoomTapDragMode = true; - if (mLogEvent) { - EventLog.writeEvent(EVENT_LOG_DOUBLE_TAP_DURATION, - (eventTime - mLastTouchUpTime), eventTime); - } - return mZoomRingController.handleDoubleTapEvent(ev); - } else { - mTouchMode = TOUCH_INIT_MODE; - mPreventDrag = mForwardTouchEvents; - if (mLogEvent && eventTime - mLastTouchUpTime < 1000) { - EventLog.writeEvent(EVENT_LOG_DOUBLE_TAP_DURATION, - (eventTime - mLastTouchUpTime), eventTime); - } - } - // don't trigger the link if zoom ring is visible - if (mTouchMode == TOUCH_INIT_MODE - && !mZoomRingController.isVisible()) { - mPrivateHandler.sendMessageDelayed(mPrivateHandler - .obtainMessage(SWITCH_TO_SHORTPRESS), TAP_TIMEOUT); - } - // Remember where the motion event started - mLastTouchX = x; - mLastTouchY = y; - mLastTouchTime = eventTime; - mVelocityTracker = VelocityTracker.obtain(); - mSnapScrollMode = SNAP_NONE; - break; - } - case MotionEvent.ACTION_MOVE: { - if (mTouchMode == TOUCH_DONE_MODE - || mTouchMode == SCROLL_ZOOM_ANIMATION_IN - || mTouchMode == SCROLL_ZOOM_ANIMATION_OUT) { - // no dragging during scroll zoom animation - break; - } - if (mTouchMode == SCROLL_ZOOM_OUT) { - // while fully zoomed out, move the virtual window - moveZoomScrollWindow(x, y); - break; - } - mVelocityTracker.addMovement(ev); - - int deltaX = (int) (mLastTouchX - x); - 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 (mPreventDrag || (deltaX * deltaX + deltaY * deltaY) - < mTouchSlopSquare) { - break; - } - - if (mTouchMode == TOUCH_SHORTPRESS_MODE - || mTouchMode == TOUCH_SHORTPRESS_START_MODE) { - mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); - } else if (mTouchMode == TOUCH_INIT_MODE) { - mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS); - } - - // if it starts nearly horizontal or vertical, enforce it - int ax = Math.abs(deltaX); - int ay = Math.abs(deltaY); - if (ax > MAX_SLOPE_FOR_DIAG * ay) { - mSnapScrollMode = SNAP_X; - mSnapPositive = deltaX > 0; - } else if (ay > MAX_SLOPE_FOR_DIAG * ax) { - mSnapScrollMode = SNAP_Y; - mSnapPositive = deltaY > 0; - } - - mTouchMode = TOUCH_DRAG_MODE; - WebViewCore.pauseUpdate(mWebViewCore); - int contentX = viewToContent((int) x + mScrollX); - int contentY = viewToContent((int) y + mScrollY); - if (inEditingMode()) { - mTextEntry.updateCachedTextfield(); - } - nativeClearFocus(contentX, contentY); - // remove the zoom anchor if there is any - if (mZoomScale != 0) { - mWebViewCore - .sendMessage(EventHub.SET_SNAP_ANCHOR, 0, 0); - } - } - - // do pan - int newScrollX = pinLocX(mScrollX + deltaX); - deltaX = newScrollX - mScrollX; - int newScrollY = pinLocY(mScrollY + deltaY); - deltaY = newScrollY - mScrollY; - boolean done = false; - if (deltaX == 0 && deltaY == 0) { - done = true; - } else { - if (mSnapScrollMode == SNAP_X || mSnapScrollMode == SNAP_Y) { - int ax = Math.abs(deltaX); - int ay = Math.abs(deltaY); - if (mSnapScrollMode == SNAP_X) { - // radical change means getting out of snap mode - if (ay > MAX_SLOPE_FOR_DIAG * ax - && ay > MIN_BREAK_SNAP_CROSS_DISTANCE) { - mSnapScrollMode = SNAP_NONE; - } - // reverse direction means lock in the snap mode - if ((ax > MAX_SLOPE_FOR_DIAG * ay) && - ((mSnapPositive && - deltaX < -mMinLockSnapReverseDistance) - || (!mSnapPositive && - deltaX > mMinLockSnapReverseDistance))) { - mSnapScrollMode = SNAP_X_LOCK; - } - } else { - // radical change means getting out of snap mode - if ((ax > MAX_SLOPE_FOR_DIAG * ay) - && ax > MIN_BREAK_SNAP_CROSS_DISTANCE) { - mSnapScrollMode = SNAP_NONE; - } - // reverse direction means lock in the snap mode - if ((ay > MAX_SLOPE_FOR_DIAG * ax) && - ((mSnapPositive && - deltaY < -mMinLockSnapReverseDistance) - || (!mSnapPositive && - deltaY > mMinLockSnapReverseDistance))) { - mSnapScrollMode = SNAP_Y_LOCK; - } - } - } - - if (mSnapScrollMode == SNAP_X - || mSnapScrollMode == SNAP_X_LOCK) { - scrollBy(deltaX, 0); - mLastTouchX = x; - } else if (mSnapScrollMode == SNAP_Y - || mSnapScrollMode == SNAP_Y_LOCK) { - scrollBy(0, deltaY); - mLastTouchY = y; - } else { - scrollBy(deltaX, deltaY); - mLastTouchX = x; - mLastTouchY = y; - } - mLastTouchTime = eventTime; - mUserScroll = true; - } - - if (ZoomRingController.useOldZoom(mContext)) { - boolean showPlusMinus = mMinZoomScale < mMaxZoomScale; - boolean showMagnify = canZoomScrollOut(); - if (mZoomControls != null && (showPlusMinus || showMagnify)) { - if (mZoomControls.getVisibility() == View.VISIBLE) { - mPrivateHandler.removeCallbacks(mZoomControlRunnable); - } else { - mZoomControls.show(showPlusMinus, showMagnify); - } - mPrivateHandler.postDelayed(mZoomControlRunnable, - ZOOM_CONTROLS_TIMEOUT); - } - } - if (done) { - // return false to indicate that we can't pan out of the - // view space - return false; - } - break; - } - case MotionEvent.ACTION_UP: { - mLastTouchUpTime = eventTime; - switch (mTouchMode) { - case TOUCH_INIT_MODE: // tap - if (mZoomRingController.isVisible()) { - // don't trigger the link if zoom ring is visible, - // but still allow the double tap - mPrivateHandler.sendMessageDelayed(mPrivateHandler - .obtainMessage(RELEASE_SINGLE_TAP, - new Boolean(false)), - DOUBLE_TAP_TIMEOUT); - break; - } - mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS); - if (getSettings().supportZoom()) { - mPrivateHandler.sendMessageDelayed(mPrivateHandler - .obtainMessage(RELEASE_SINGLE_TAP, - new Boolean(true)), - DOUBLE_TAP_TIMEOUT); - } else { - // do short press now - mTouchMode = TOUCH_DONE_MODE; - doShortPress(); - } - break; - case TOUCH_SELECT_MODE: - commitCopy(); - mTouchSelection = false; - break; - case SCROLL_ZOOM_ANIMATION_IN: - case SCROLL_ZOOM_ANIMATION_OUT: - // no action during scroll animation - break; - case SCROLL_ZOOM_OUT: - if (LOGV_ENABLED) { - Log.v(LOGTAG, "ACTION_UP SCROLL_ZOOM_OUT" - + " eventTime - mLastTouchTime=" - + (eventTime - mLastTouchTime)); - } - // for now, always zoom back when the drag completes - if (true || eventTime - mLastTouchTime < TAP_TIMEOUT) { - // but if we tap, zoom in where we tap - if (eventTime - mLastTouchTime < TAP_TIMEOUT) { - zoomScrollTap(x, y); - } - // start zooming in back to the original view - setZoomScrollIn(); - mTouchMode = SCROLL_ZOOM_ANIMATION_IN; - invalidate(); - } - break; - case TOUCH_SHORTPRESS_START_MODE: - case TOUCH_SHORTPRESS_MODE: { - mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); - if (eventTime - mLastTouchTime < TAP_TIMEOUT - && getSettings().supportZoom()) { - // Note: window manager will not release ACTION_UP - // until all the previous action events are - // returned. If GC happens, it can cause - // SWITCH_TO_SHORTPRESS message fired before - // ACTION_UP sent even time stamp of ACTION_UP is - // less than the tap time out. We need to treat this - // as tap instead of short press. - mTouchMode = TOUCH_INIT_MODE; - mPrivateHandler.sendMessageDelayed(mPrivateHandler - .obtainMessage(RELEASE_SINGLE_TAP, - new Boolean(true)), - DOUBLE_TAP_TIMEOUT); - } else { - mTouchMode = TOUCH_DONE_MODE; - doShortPress(); - } - break; - } - case TOUCH_DRAG_MODE: - // if the user waits a while w/o moving before the - // up, we don't want to do a fling - if (eventTime - mLastTouchTime <= MIN_FLING_TIME) { - mVelocityTracker.addMovement(ev); - doFling(); - break; - } - WebViewCore.resumeUpdate(mWebViewCore); - break; - case TOUCH_DRAG_START_MODE: - case TOUCH_DONE_MODE: - // do nothing - break; - } - // we also use mVelocityTracker == null to tell us that we are - // not "moving around", so we can take the slower/prettier - // mode in the drawing code - if (mVelocityTracker != null) { - mVelocityTracker.recycle(); - mVelocityTracker = null; - } - break; - } - case MotionEvent.ACTION_CANCEL: { - // we also use mVelocityTracker == null to tell us that we are - // not "moving around", so we can take the slower/prettier - // mode in the drawing code - if (mVelocityTracker != null) { - mVelocityTracker.recycle(); - mVelocityTracker = null; - } - if (mTouchMode == SCROLL_ZOOM_OUT || - mTouchMode == SCROLL_ZOOM_ANIMATION_IN) { - scrollTo(mZoomScrollX, mZoomScrollY); - } else if (mTouchMode == TOUCH_DRAG_MODE) { - WebViewCore.resumeUpdate(mWebViewCore); - } - mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS); - mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); - mPrivateHandler.removeMessages(RELEASE_SINGLE_TAP); - mTouchMode = TOUCH_DONE_MODE; - int contentX = viewToContent((int) mLastTouchX + mScrollX); - int contentY = viewToContent((int) mLastTouchY + mScrollY); - if (inEditingMode()) { - mTextEntry.updateCachedTextfield(); - } - nativeClearFocus(contentX, contentY); - break; - } - } - return true; - } - - private long mTrackballFirstTime = 0; - private long mTrackballLastTime = 0; - private float mTrackballRemainsX = 0.0f; - private float mTrackballRemainsY = 0.0f; - 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; - private static final int TRACKBALL_SCALE = 400; - private static final int TRACKBALL_SCROLL_COUNT = 5; - private static final int TRACKBALL_MOVE_COUNT = 10; - private static final int TRACKBALL_MULTIPLIER = 3; - private static final int SELECT_CURSOR_OFFSET = 16; - private int mSelectX = 0; - private int mSelectY = 0; - private boolean mShiftIsPressed = false; - private boolean mTrackballDown = false; - private long mTrackballUpTime = 0; - private long mLastFocusTime = 0; - private Rect mLastFocusBounds; - - // Set by default; BrowserActivity clears to interpret trackball data - // directly for movement. Currently, the framework only passes - // arrow key events, not trackball events, from one child to the next - private boolean mMapTrackballToArrowKeys = true; - - public void setMapTrackballToArrowKeys(boolean setMap) { - mMapTrackballToArrowKeys = setMap; - } - - void resetTrackballTime() { - mTrackballLastTime = 0; - } - - @Override - public boolean onTrackballEvent(MotionEvent ev) { - long time = ev.getEventTime(); - 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); - mTrackballDown = true; - if (mNativeClass != 0) { - nativeRecordButtons(hasFocus() && hasWindowFocus(), true, true); - } - if (time - mLastFocusTime <= TRACKBALL_TIMEOUT - && !mLastFocusBounds.equals(nativeGetFocusRingBounds())) { - nativeSelectBestAt(mLastFocusBounds); - } - if (LOGV_ENABLED) { - Log.v(LOGTAG, "onTrackballEvent down ev=" + ev - + " time=" + time - + " mLastFocusTime=" + mLastFocusTime); - } - if (isInTouchMode()) requestFocusFromTouch(); - return false; // let common code in onKeyDown at it - } - if (ev.getAction() == MotionEvent.ACTION_UP) { - // LONG_PRESS_ENTER is set in common onKeyDown - mPrivateHandler.removeMessages(LONG_PRESS_ENTER); - mTrackballDown = false; - mTrackballUpTime = time; - if (mShiftIsPressed) { - if (mExtendSelection) { - commitCopy(); - } else { - mExtendSelection = true; - } - } - if (LOGV_ENABLED) { - Log.v(LOGTAG, "onTrackballEvent up ev=" + ev - + " time=" + time - ); - } - return false; // let common code in onKeyUp at it - } - if (mMapTrackballToArrowKeys && mShiftIsPressed == false) { - if (LOGV_ENABLED) Log.v(LOGTAG, "onTrackballEvent gmail quit"); - return false; - } - // no move if we're still waiting on SWITCH_TO_ENTER timeout - if (mTouchMode == TOUCH_DOUBLECLICK_MODE) { - if (LOGV_ENABLED) Log.v(LOGTAG, "onTrackballEvent 2 click quit"); - return true; - } - if (mTrackballDown) { - if (LOGV_ENABLED) Log.v(LOGTAG, "onTrackballEvent down quit"); - return true; // discard move if trackball is down - } - if (time - mTrackballUpTime < TRACKBALL_TIMEOUT) { - if (LOGV_ENABLED) Log.v(LOGTAG, "onTrackballEvent up timeout quit"); - return true; - } - // TODO: alternatively we can do panning as touch does - switchOutDrawHistory(); - if (time - mTrackballLastTime > TRACKBALL_TIMEOUT) { - if (LOGV_ENABLED) { - Log.v(LOGTAG, "onTrackballEvent time=" - + time + " last=" + mTrackballLastTime); - } - mTrackballFirstTime = time; - mTrackballXMove = mTrackballYMove = 0; - } - mTrackballLastTime = time; - if (LOGV_ENABLED) { - Log.v(LOGTAG, "onTrackballEvent ev=" + ev + " time=" + time); - } - mTrackballRemainsX += ev.getX(); - mTrackballRemainsY += ev.getY(); - doTrackball(time); - return true; - } - - void moveSelection(float xRate, float yRate) { - if (mNativeClass == 0) - return; - int width = getViewWidth(); - int height = getViewHeight(); - mSelectX += scaleTrackballX(xRate, width); - mSelectY += scaleTrackballY(yRate, height); - int maxX = width + mScrollX; - int maxY = height + mScrollY; - mSelectX = Math.min(maxX, Math.max(mScrollX - SELECT_CURSOR_OFFSET - , mSelectX)); - mSelectY = Math.min(maxY, Math.max(mScrollY - SELECT_CURSOR_OFFSET - , mSelectY)); - if (LOGV_ENABLED) { - Log.v(LOGTAG, "moveSelection" - + " mSelectX=" + mSelectX - + " mSelectY=" + mSelectY - + " mScrollX=" + mScrollX - + " mScrollY=" + mScrollY - + " xRate=" + xRate - + " yRate=" + yRate - ); - } - nativeMoveSelection(viewToContent(mSelectX) - , viewToContent(mSelectY), mExtendSelection); - int scrollX = mSelectX < mScrollX ? -SELECT_CURSOR_OFFSET - : mSelectX > maxX - SELECT_CURSOR_OFFSET ? SELECT_CURSOR_OFFSET - : 0; - int scrollY = mSelectY < mScrollY ? -SELECT_CURSOR_OFFSET - : mSelectY > maxY - SELECT_CURSOR_OFFSET ? SELECT_CURSOR_OFFSET - : 0; - pinScrollBy(scrollX, scrollY, true, 0); - Rect select = new Rect(mSelectX, mSelectY, mSelectX + 1, mSelectY + 1); - requestRectangleOnScreen(select); - invalidate(); - } - - private int scaleTrackballX(float xRate, int width) { - int xMove = (int) (xRate / TRACKBALL_SCALE * width); - int nextXMove = xMove; - if (xMove > 0) { - if (xMove > mTrackballXMove) { - xMove -= mTrackballXMove; - } - } else if (xMove < mTrackballXMove) { - xMove -= mTrackballXMove; - } - mTrackballXMove = nextXMove; - return xMove; - } - - private int scaleTrackballY(float yRate, int height) { - int yMove = (int) (yRate / TRACKBALL_SCALE * height); - int nextYMove = yMove; - if (yMove > 0) { - if (yMove > mTrackballYMove) { - yMove -= mTrackballYMove; - } - } else if (yMove < mTrackballYMove) { - yMove -= mTrackballYMove; - } - mTrackballYMove = nextYMove; - return yMove; - } - - private int keyCodeToSoundsEffect(int keyCode) { - switch(keyCode) { - case KeyEvent.KEYCODE_DPAD_UP: - return SoundEffectConstants.NAVIGATION_UP; - case KeyEvent.KEYCODE_DPAD_RIGHT: - return SoundEffectConstants.NAVIGATION_RIGHT; - case KeyEvent.KEYCODE_DPAD_DOWN: - return SoundEffectConstants.NAVIGATION_DOWN; - case KeyEvent.KEYCODE_DPAD_LEFT: - return SoundEffectConstants.NAVIGATION_LEFT; - } - throw new IllegalArgumentException("keyCode must be one of " + - "{KEYCODE_DPAD_UP, KEYCODE_DPAD_RIGHT, KEYCODE_DPAD_DOWN, " + - "KEYCODE_DPAD_LEFT}."); - } - - private void doTrackball(long time) { - int elapsed = (int) (mTrackballLastTime - mTrackballFirstTime); - if (elapsed == 0) { - elapsed = TRACKBALL_TIMEOUT; - } - float xRate = mTrackballRemainsX * 1000 / elapsed; - float yRate = mTrackballRemainsY * 1000 / elapsed; - if (mShiftIsPressed) { - moveSelection(xRate, yRate); - mTrackballRemainsX = mTrackballRemainsY = 0; - return; - } - float ax = Math.abs(xRate); - float ay = Math.abs(yRate); - float maxA = Math.max(ax, ay); - if (LOGV_ENABLED) { - Log.v(LOGTAG, "doTrackball elapsed=" + elapsed - + " xRate=" + xRate - + " yRate=" + yRate - + " mTrackballRemainsX=" + mTrackballRemainsX - + " mTrackballRemainsY=" + mTrackballRemainsY); - } - int width = mContentWidth - getViewWidth(); - int height = mContentHeight - getViewHeight(); - if (width < 0) width = 0; - if (height < 0) height = 0; - if (mTouchMode == SCROLL_ZOOM_OUT) { - int oldX = mZoomScrollX; - int oldY = mZoomScrollY; - int maxWH = Math.max(width, height); - mZoomScrollX += scaleTrackballX(xRate, maxWH); - mZoomScrollY += scaleTrackballY(yRate, maxWH); - if (LOGV_ENABLED) { - Log.v(LOGTAG, "doTrackball SCROLL_ZOOM_OUT" - + " mZoomScrollX=" + mZoomScrollX - + " mZoomScrollY=" + mZoomScrollY); - } - mZoomScrollX = Math.min(width, Math.max(0, mZoomScrollX)); - mZoomScrollY = Math.min(height, Math.max(0, mZoomScrollY)); - if (oldX != mZoomScrollX || oldY != mZoomScrollY) { - invalidate(); - } - mTrackballRemainsX = mTrackballRemainsY = 0; - return; - } - ax = Math.abs(mTrackballRemainsX * TRACKBALL_MULTIPLIER); - ay = Math.abs(mTrackballRemainsY * TRACKBALL_MULTIPLIER); - maxA = Math.max(ax, ay); - int count = Math.max(0, (int) maxA); - int oldScrollX = mScrollX; - int oldScrollY = mScrollY; - if (count > 0) { - int selectKeyCode = ax < ay ? mTrackballRemainsY < 0 ? - KeyEvent.KEYCODE_DPAD_UP : KeyEvent.KEYCODE_DPAD_DOWN : - mTrackballRemainsX < 0 ? KeyEvent.KEYCODE_DPAD_LEFT : - KeyEvent.KEYCODE_DPAD_RIGHT; - count = Math.min(count, TRACKBALL_MOVE_COUNT); - if (LOGV_ENABLED) { - Log.v(LOGTAG, "doTrackball keyCode=" + selectKeyCode - + " count=" + count - + " mTrackballRemainsX=" + mTrackballRemainsX - + " mTrackballRemainsY=" + mTrackballRemainsY); - } - if (navHandledKey(selectKeyCode, count, false, time)) { - playSoundEffect(keyCodeToSoundsEffect(selectKeyCode)); - } - mTrackballRemainsX = mTrackballRemainsY = 0; - } - if (count >= TRACKBALL_SCROLL_COUNT) { - int xMove = scaleTrackballX(xRate, width); - int yMove = scaleTrackballY(yRate, height); - if (LOGV_ENABLED) { - Log.v(LOGTAG, "doTrackball pinScrollBy" - + " count=" + count - + " xMove=" + xMove + " yMove=" + yMove - + " mScrollX-oldScrollX=" + (mScrollX-oldScrollX) - + " mScrollY-oldScrollY=" + (mScrollY-oldScrollY) - ); - } - if (Math.abs(mScrollX - oldScrollX) > Math.abs(xMove)) { - xMove = 0; - } - if (Math.abs(mScrollY - oldScrollY) > Math.abs(yMove)) { - yMove = 0; - } - if (xMove != 0 || yMove != 0) { - pinScrollBy(xMove, yMove, true, 0); - } - mUserScroll = true; - } - mWebViewCore.sendMessage(EventHub.UNBLOCK_FOCUS); - } - - public void flingScroll(int vx, int vy) { - int maxX = Math.max(computeHorizontalScrollRange() - getViewWidth(), 0); - int maxY = Math.max(computeVerticalScrollRange() - getViewHeight(), 0); - - mScroller.fling(mScrollX, mScrollY, vx, vy, 0, maxX, 0, maxY); - invalidate(); - } - - private void doFling() { - if (mVelocityTracker == null) { - return; - } - int maxX = Math.max(computeHorizontalScrollRange() - getViewWidth(), 0); - int maxY = Math.max(computeVerticalScrollRange() - getViewHeight(), 0); - - mVelocityTracker.computeCurrentVelocity(1000); - int vx = (int) mVelocityTracker.getXVelocity(); - int vy = (int) mVelocityTracker.getYVelocity(); - - if (mSnapScrollMode != SNAP_NONE) { - if (mSnapScrollMode == SNAP_X || mSnapScrollMode == SNAP_X_LOCK) { - vy = 0; - } else { - vx = 0; - } - } - - if (true /* EMG release: make our fling more like Maps' */) { - // maps cuts their velocity in half - vx = vx * 3 / 4; - vy = vy * 3 / 4; - } - - mScroller.fling(mScrollX, mScrollY, -vx, -vy, 0, maxX, 0, maxY); - // TODO: duration is calculated based on velocity, if the range is - // small, the animation will stop before duration is up. We may - // want to calculate how long the animation is going to run to precisely - // resume the webcore update. - final int time = mScroller.getDuration(); - mPrivateHandler.sendEmptyMessageDelayed(RESUME_WEBCORE_UPDATE, time); - invalidate(); - } - - private boolean zoomWithPreview(float scale) { - float oldScale = mActualScale; - - // snap to 100% if it is close - if (scale > 0.95f && scale < 1.05f) { - scale = 1.0f; - } - - setNewZoomScale(scale, false); - - if (oldScale != mActualScale) { - // use mZoomPickerScale to see zoom preview first - mZoomStart = SystemClock.uptimeMillis(); - mInvInitialZoomScale = 1.0f / oldScale; - mInvFinalZoomScale = 1.0f / mActualScale; - mZoomScale = mActualScale; - invalidate(); - return true; - } else { - return false; - } - } - - /** - * Returns a view containing zoom controls i.e. +/- buttons. The caller is - * in charge of installing this view to the view hierarchy. This view will - * become visible when the user starts scrolling via touch and fade away if - * the user does not interact with it. - */ - public View getZoomControls() { - if (!getSettings().supportZoom()) { - Log.w(LOGTAG, "This WebView doesn't support zoom."); - return null; - } - if (mZoomControls == null) { - mZoomControls = createZoomControls(); - - /* - * need to be set to VISIBLE first so that getMeasuredHeight() in - * {@link #onSizeChanged()} can return the measured value for proper - * layout. - */ - mZoomControls.setVisibility(View.VISIBLE); - mZoomControlRunnable = new Runnable() { - public void run() { - - /* Don't dismiss the controls if the user has - * focus on them. Wait and check again later. - */ - if (!mZoomControls.hasFocus()) { - mZoomControls.hide(); - } else { - mPrivateHandler.removeCallbacks(mZoomControlRunnable); - mPrivateHandler.postDelayed(mZoomControlRunnable, - ZOOM_CONTROLS_TIMEOUT); - } - } - }; - } - return mZoomControls; - } - - /** - * @hide pending API council? Assuming we make ZoomRingController itself - * public, which I think we will. - */ - public ZoomRingController getZoomRingController() { - return mZoomRingController; - } - - /** - * Perform zoom in in the webview - * @return TRUE if zoom in succeeds. FALSE if no zoom changes. - */ - public boolean zoomIn() { - // TODO: alternatively we can disallow this during draw history mode - switchOutDrawHistory(); - return zoomWithPreview(mActualScale * 1.25f); - } - - /** - * Perform zoom out in the webview - * @return TRUE if zoom out succeeds. FALSE if no zoom changes. - */ - public boolean zoomOut() { - // TODO: alternatively we can disallow this during draw history mode - switchOutDrawHistory(); - return zoomWithPreview(mActualScale * 0.8f); - } - - private ExtendedZoomControls createZoomControls() { - ExtendedZoomControls zoomControls = new ExtendedZoomControls(mContext - , null); - zoomControls.setOnZoomInClickListener(new OnClickListener() { - public void onClick(View v) { - // reset time out - mPrivateHandler.removeCallbacks(mZoomControlRunnable); - mPrivateHandler.postDelayed(mZoomControlRunnable, - ZOOM_CONTROLS_TIMEOUT); - zoomIn(); - } - }); - zoomControls.setOnZoomOutClickListener(new OnClickListener() { - public void onClick(View v) { - // reset time out - mPrivateHandler.removeCallbacks(mZoomControlRunnable); - mPrivateHandler.postDelayed(mZoomControlRunnable, - ZOOM_CONTROLS_TIMEOUT); - zoomOut(); - } - }); - zoomControls.setOnZoomMagnifyClickListener(new OnClickListener() { - public void onClick(View v) { - // Hide the zoom ring - mZoomRingController.setVisible(false); - - mPrivateHandler.removeCallbacks(mZoomControlRunnable); - mPrivateHandler.postDelayed(mZoomControlRunnable, - ZOOM_CONTROLS_TIMEOUT); - zoomScrollOut(); - } - }); - return zoomControls; - } - - private void updateSelection() { - if (mNativeClass == 0) { - return; - } - // 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(); - Rect rect = new Rect(contentX - contentSize, contentY - contentSize, - contentX + contentSize, contentY + contentSize); - // If we were already focused on a textfield, update its cache. - if (inEditingMode()) { - mTextEntry.updateCachedTextfield(); - } - nativeSelectBestAt(rect); - } - - /*package*/ void shortPressOnTextField() { - if (inEditingMode()) { - View v = mTextEntry; - int x = viewToContent((v.getLeft() + v.getRight()) >> 1); - int y = viewToContent((v.getTop() + v.getBottom()) >> 1); - int contentSize = ViewConfiguration.get(getContext()).getScaledTouchSlop(); - nativeMotionUp(x, y, contentSize, true); - } - } - - private void doShortPress() { - if (mNativeClass == 0) { - 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.get(getContext()).getScaledTouchSlop(); - if (nativeMotionUp(contentX, contentY, contentSize, true)) { - if (mLogEvent) { - Checkin.updateStats(mContext.getContentResolver(), - Checkin.Stats.Tag.BROWSER_SNAP_CENTER, 1, 0.0); - } - } - if (nativeUpdateFocusNode() && !mFocusNode.mIsTextField - && !mFocusNode.mIsTextArea) { - playSoundEffect(SoundEffectConstants.CLICK); - } - } - - // Called by JNI to handle a touch on a node representing an email address, - // address, or phone number - private void overrideLoading(String url) { - mCallbackProxy.uiOverrideUrlLoading(url); - } - - @Override - public boolean requestFocus(int direction, Rect previouslyFocusedRect) { - boolean result = false; - if (inEditingMode()) { - result = mTextEntry.requestFocus(direction, previouslyFocusedRect); - } else { - result = super.requestFocus(direction, previouslyFocusedRect); - if (mWebViewCore.getSettings().getNeedInitialFocus()) { - // For cases such as GMail, where we gain focus from a direction, - // we want to move to the first available link. - // FIXME: If there are no visible links, we may not want to - int fakeKeyDirection = 0; - switch(direction) { - case View.FOCUS_UP: - fakeKeyDirection = KeyEvent.KEYCODE_DPAD_UP; - break; - case View.FOCUS_DOWN: - fakeKeyDirection = KeyEvent.KEYCODE_DPAD_DOWN; - break; - case View.FOCUS_LEFT: - fakeKeyDirection = KeyEvent.KEYCODE_DPAD_LEFT; - break; - case View.FOCUS_RIGHT: - fakeKeyDirection = KeyEvent.KEYCODE_DPAD_RIGHT; - break; - default: - return result; - } - if (mNativeClass != 0 && !nativeUpdateFocusNode()) { - navHandledKey(fakeKeyDirection, 1, true, 0); - } - } - } - return result; - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - - int heightMode = MeasureSpec.getMode(heightMeasureSpec); - int heightSize = MeasureSpec.getSize(heightMeasureSpec); - int widthMode = MeasureSpec.getMode(widthMeasureSpec); - int widthSize = MeasureSpec.getSize(widthMeasureSpec); - - int measuredHeight = heightSize; - int measuredWidth = widthSize; - - // Grab the content size from WebViewCore. - int contentHeight = mContentHeight; - int contentWidth = mContentWidth; - -// Log.d(LOGTAG, "------- measure " + heightMode); - - if (heightMode != MeasureSpec.EXACTLY) { - mHeightCanMeasure = true; - measuredHeight = contentHeight; - if (heightMode == MeasureSpec.AT_MOST) { - // If we are larger than the AT_MOST height, then our height can - // no longer be measured and we should scroll internally. - if (measuredHeight > heightSize) { - measuredHeight = heightSize; - mHeightCanMeasure = false; - } - } - } else { - mHeightCanMeasure = false; - } - if (mNativeClass != 0) { - nativeSetHeightCanMeasure(mHeightCanMeasure); - } - // For the width, always use the given size unless unspecified. - if (widthMode == MeasureSpec.UNSPECIFIED) { - mWidthCanMeasure = true; - measuredWidth = contentWidth; - } else { - mWidthCanMeasure = false; - } - - synchronized (this) { - setMeasuredDimension(measuredWidth, measuredHeight); - } - } - - @Override - public boolean requestChildRectangleOnScreen(View child, - Rect rect, - boolean immediate) { - rect.offset(child.getLeft() - child.getScrollX(), - child.getTop() - child.getScrollY()); - - int height = getHeight() - getHorizontalScrollbarHeight(); - int screenTop = mScrollY; - int screenBottom = screenTop + height; - - int scrollYDelta = 0; - - if (rect.bottom > screenBottom && rect.top > screenTop) { - if (rect.height() > height) { - scrollYDelta += (rect.top - screenTop); - } else { - scrollYDelta += (rect.bottom - screenBottom); - } - } else if (rect.top < screenTop) { - scrollYDelta -= (screenTop - rect.top); - } - - int width = getWidth() - getVerticalScrollbarWidth(); - int screenLeft = mScrollX; - int screenRight = screenLeft + width; - - int scrollXDelta = 0; - - if (rect.right > screenRight && rect.left > screenLeft) { - if (rect.width() > width) { - scrollXDelta += (rect.left - screenLeft); - } else { - scrollXDelta += (rect.right - screenRight); - } - } else if (rect.left < screenLeft) { - scrollXDelta -= (screenLeft - rect.left); - } - - if ((scrollYDelta | scrollXDelta) != 0) { - return pinScrollBy(scrollXDelta, scrollYDelta, !immediate, 0); - } - - return false; - } - - /* package */ void replaceTextfieldText(int oldStart, int oldEnd, - String replace, int newStart, int newEnd) { - HashMap arg = new HashMap(); - arg.put("focusData", new WebViewCore.FocusData(mFocusData)); - arg.put("replace", replace); - arg.put("start", new Integer(newStart)); - arg.put("end", new Integer(newEnd)); - mWebViewCore.sendMessage(EventHub.REPLACE_TEXT, oldStart, oldEnd, arg); - } - - /* package */ void passToJavaScript(String currentText, KeyEvent event) { - HashMap arg = new HashMap(); - arg.put("focusData", new WebViewCore.FocusData(mFocusData)); - arg.put("event", event); - arg.put("currentText", currentText); - // Increase our text generation number, and pass it to webcore thread - mTextGeneration++; - mWebViewCore.sendMessage(EventHub.PASS_TO_JS, mTextGeneration, 0, arg); - // WebKit's document state is not saved until about to leave the page. - // To make sure the host application, like Browser, has the up to date - // document state when it goes to background, we force to save the - // document state. - mWebViewCore.removeMessages(EventHub.SAVE_DOCUMENT_STATE); - mWebViewCore.sendMessageDelayed(EventHub.SAVE_DOCUMENT_STATE, - new WebViewCore.FocusData(mFocusData), 1000); - } - - /* package */ WebViewCore getWebViewCore() { - return mWebViewCore; - } - - //------------------------------------------------------------------------- - // Methods can be called from a separate thread, like WebViewCore - // If it needs to call the View system, it has to send message. - //------------------------------------------------------------------------- - - /** - * General handler to receive message coming from webkit thread - */ - class PrivateHandler extends Handler { - @Override - public void handleMessage(Message msg) { - if (LOGV_ENABLED) { - Log.v(LOGTAG, msg.what < REMEMBER_PASSWORD || msg.what - > INVAL_RECT_MSG_ID ? Integer.toString(msg.what) - : HandlerDebugString[msg.what - REMEMBER_PASSWORD]); - } - switch (msg.what) { - case REMEMBER_PASSWORD: { - mDatabase.setUsernamePassword( - msg.getData().getString("host"), - msg.getData().getString("username"), - msg.getData().getString("password")); - ((Message) msg.obj).sendToTarget(); - break; - } - case NEVER_REMEMBER_PASSWORD: { - mDatabase.setUsernamePassword( - msg.getData().getString("host"), null, null); - ((Message) msg.obj).sendToTarget(); - break; - } - case SWITCH_TO_SHORTPRESS: { - if (mTouchMode == TOUCH_INIT_MODE) { - mTouchMode = TOUCH_SHORTPRESS_START_MODE; - updateSelection(); - } - break; - } - case SWITCH_TO_LONGPRESS: { - mTouchMode = TOUCH_DONE_MODE; - performLongClick(); - updateTextEntry(); - break; - } - case RELEASE_SINGLE_TAP: { - mTouchMode = TOUCH_DONE_MODE; - if ((Boolean)msg.obj) { - doShortPress(); - } - break; - } - case SWITCH_TO_ENTER: - if (LOGV_ENABLED) Log.v(LOGTAG, "SWITCH_TO_ENTER"); - mTouchMode = TOUCH_DONE_MODE; - onKeyUp(KeyEvent.KEYCODE_ENTER - , new KeyEvent(KeyEvent.ACTION_UP - , KeyEvent.KEYCODE_ENTER)); - break; - case SCROLL_BY_MSG_ID: - setContentScrollBy(msg.arg1, msg.arg2, (Boolean) msg.obj); - break; - case SYNC_SCROLL_TO_MSG_ID: - if (mUserScroll) { - // if user has scrolled explicitly, don't sync the - // scroll position any more - mUserScroll = false; - break; - } - // fall through - case SCROLL_TO_MSG_ID: - if (setContentScrollTo(msg.arg1, msg.arg2)) { - // if we can't scroll to the exact position due to pin, - // send a message to WebCore to re-scroll when we get a - // new picture - mUserScroll = false; - mWebViewCore.sendMessage(EventHub.SYNC_SCROLL, - msg.arg1, msg.arg2); - } - break; - case SPAWN_SCROLL_TO_MSG_ID: - spawnContentScrollTo(msg.arg1, msg.arg2); - break; - case NEW_PICTURE_MSG_ID: - // called for new content - final WebViewCore.DrawData draw = - (WebViewCore.DrawData) msg.obj; - final Point viewSize = draw.mViewPoint; - if (mZoomScale > 0) { - // 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); - } - } - mMinContentWidth = msg.arg1; - if (mMinContentWidth > MAX_FLOAT_CONTENT_WIDTH) { - mMinZoomScale = (float) getViewWidth() - / draw.mWidthHeight.x; - } - // We update the layout (i.e. request a layout from the - // view system) if the last view size that we sent to - // WebCore matches the view size of the picture we just - // received in the fixed dimension. - final boolean updateLayout = viewSize.x == mLastWidthSent - && viewSize.y == mLastHeightSent; - 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()); - } - break; - case WEBCORE_INITIALIZED_MSG_ID: - // nativeCreate sets mNativeClass to a non-zero value - nativeCreate(msg.arg1); - break; - case UPDATE_TEXTFIELD_TEXT_MSG_ID: - // Make sure that the textfield is currently focused - // and representing the same node as the pointer. - if (inEditingMode() && - mTextEntry.isSameTextField(msg.arg1)) { - if (msg.getData().getBoolean("password")) { - Spannable text = (Spannable) mTextEntry.getText(); - int start = Selection.getSelectionStart(text); - int end = Selection.getSelectionEnd(text); - mTextEntry.setInPassword(true); - // Restore the selection, which may have been - // ruined by setInPassword. - Spannable pword = (Spannable) mTextEntry.getText(); - Selection.setSelection(pword, start, end); - // If the text entry has created more events, ignore - // this one. - } else if (msg.arg2 == mTextGeneration) { - mTextEntry.setTextAndKeepSelection( - (String) msg.obj); - } - } - break; - case DID_FIRST_LAYOUT_MSG_ID: - if (mNativeClass == 0) { - break; - } -// Do not reset the focus or clear the text; the user may have already -// navigated or entered text at this point. The focus should have gotten -// reset, if need be, when the focus cache was built. Similarly, the text -// view should already be torn down and rebuilt if needed. -// nativeResetFocus(); -// clearTextEntry(); - HashMap scaleLimit = (HashMap) msg.obj; - int minScale = (Integer) scaleLimit.get("minScale"); - if (minScale == 0) { - mMinZoomScale = DEFAULT_MIN_ZOOM_SCALE; - } else { - mMinZoomScale = (float) (minScale / 100.0); - } - int maxScale = (Integer) scaleLimit.get("maxScale"); - if (maxScale == 0) { - mMaxZoomScale = DEFAULT_MAX_ZOOM_SCALE; - } else { - mMaxZoomScale = (float) (maxScale / 100.0); - } - // If history Picture is drawn, don't update zoomWidth - if (mDrawHistory) { - break; - } - int width = getViewWidth(); - if (width == 0) { - break; - } - 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; - } else { - if (mWebViewCore.getSettings().getUseWideViewPort()) { - // force viewSizeChanged by setting mLastWidthSent - // to 0 - mLastWidthSent = 0; - } - 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; - case MARK_NODE_INVALID_ID: - nativeMarkNodeInvalid(msg.arg1); - break; - case NOTIFY_FOCUS_SET_MSG_ID: - if (mNativeClass != 0) { - nativeNotifyFocusSet(inEditingMode()); - } - break; - case UPDATE_TEXT_ENTRY_MSG_ID: - // this is sent after finishing resize in WebViewCore. Make - // sure the text edit box is still on the screen. - boolean alreadyThere = inEditingMode(); - if (alreadyThere && nativeUpdateFocusNode()) { - FocusNode node = mFocusNode; - if (node.mIsTextField || node.mIsTextArea) { - mTextEntry.bringIntoView(); - } - } - updateTextEntry(); - break; - case RECOMPUTE_FOCUS_MSG_ID: - if (mNativeClass != 0) { - nativeRecomputeFocus(); - } - break; - case INVAL_RECT_MSG_ID: { - Rect r = (Rect)msg.obj; - if (r == null) { - invalidate(); - } else { - // we need to scale r from content into view coords, - // which viewInvalidate() does for us - viewInvalidate(r.left, r.top, r.right, r.bottom); - } - break; - } - case UPDATE_TEXT_ENTRY_ADAPTER: - HashMap data = (HashMap) msg.obj; - if (mTextEntry.isSameTextField(msg.arg1)) { - AutoCompleteAdapter adapter = - (AutoCompleteAdapter) data.get("adapter"); - mTextEntry.setAdapterCustom(adapter); - } - break; - case UPDATE_CLIPBOARD: - String str = (String) msg.obj; - if (LOGV_ENABLED) { - Log.v(LOGTAG, "UPDATE_CLIPBOARD " + str); - } - try { - IClipboard clip = IClipboard.Stub.asInterface( - ServiceManager.getService("clipboard")); - clip.setClipboardText(str); - } catch (android.os.RemoteException e) { - Log.e(LOGTAG, "Clipboard failed", e); - } - break; - case RESUME_WEBCORE_UPDATE: - WebViewCore.resumeUpdate(mWebViewCore); - break; - - case LONG_PRESS_ENTER: - // as this is shared by keydown and trackballdown, reset all - // the states - mGotEnterDown = false; - mTrackballDown = false; - // 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: - if (msg.arg1 == MotionEvent.ACTION_DOWN) { - mPreventDrag = msg.arg2 == 1; - if (mPreventDrag) { - mTouchMode = TOUCH_DONE_MODE; - } - } - break; - - case DISMISS_ZOOM_RING_TUTORIAL: - mZoomRingController.finishZoomTutorial(); - break; - - default: - super.handleMessage(msg); - break; - } - } - } - - // Class used to use a dropdown for a <select> element - private class InvokeListBox implements Runnable { - // Strings for the labels in the listbox. - private String[] mArray; - // Array representing whether each item is enabled. - private boolean[] mEnableArray; - // Whether the listbox allows multiple selection. - private boolean mMultiple; - // Passed in to a list with multiple selection to tell - // which items are selected. - private int[] mSelectedArray; - // Passed in to a list with single selection to tell - // where the initial selection is. - private int mSelection; - - private Container[] mContainers; - - // Need these to provide stable ids to my ArrayAdapter, - // which normally does not have stable ids. (Bug 1250098) - private class Container extends Object { - String mString; - boolean mEnabled; - int mId; - - public String toString() { - return mString; - } - } - - /** - * Subclass ArrayAdapter so we can disable OptionGroupLabels, - * and allow filtering. - */ - private class MyArrayListAdapter extends ArrayAdapter<Container> { - public MyArrayListAdapter(Context context, Container[] objects, boolean multiple) { - super(context, - multiple ? com.android.internal.R.layout.select_dialog_multichoice : - com.android.internal.R.layout.select_dialog_singlechoice, - objects); - } - - @Override - public boolean hasStableIds() { - return true; - } - - private Container item(int position) { - if (position < 0 || position >= getCount()) { - return null; - } - return (Container) getItem(position); - } - - @Override - public long getItemId(int position) { - Container item = item(position); - if (item == null) { - return -1; - } - return item.mId; - } - - @Override - public boolean areAllItemsEnabled() { - return false; - } - - @Override - public boolean isEnabled(int position) { - Container item = item(position); - if (item == null) { - return false; - } - return item.mEnabled; - } - } - - private InvokeListBox(String[] array, - boolean[] enabled, int[] selected) { - mMultiple = true; - mSelectedArray = selected; - - int length = array.length; - mContainers = new Container[length]; - for (int i = 0; i < length; i++) { - mContainers[i] = new Container(); - mContainers[i].mString = array[i]; - mContainers[i].mEnabled = enabled[i]; - mContainers[i].mId = i; - } - } - - private InvokeListBox(String[] array, boolean[] enabled, int - selection) { - mSelection = selection; - mMultiple = false; - - int length = array.length; - mContainers = new Container[length]; - for (int i = 0; i < length; i++) { - mContainers[i] = new Container(); - mContainers[i].mString = array[i]; - mContainers[i].mEnabled = enabled[i]; - mContainers[i].mId = i; - } - } - - public void run() { - final ListView listView = (ListView) LayoutInflater.from(mContext) - .inflate(com.android.internal.R.layout.select_dialog, null); - final MyArrayListAdapter adapter = new - MyArrayListAdapter(mContext, mContainers, mMultiple); - AlertDialog.Builder b = new AlertDialog.Builder(mContext) - .setView(listView).setCancelable(true) - .setInverseBackgroundForced(true); - - if (mMultiple) { - b.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - mWebViewCore.sendMessage( - EventHub.LISTBOX_CHOICES, - adapter.getCount(), 0, - listView.getCheckedItemPositions()); - }}); - b.setNegativeButton(android.R.string.cancel, null); - } - final AlertDialog dialog = b.create(); - listView.setAdapter(adapter); - listView.setFocusableInTouchMode(true); - // There is a bug (1250103) where the checks in a ListView with - // multiple items selected are associated with the positions, not - // the ids, so the items do not properly retain their checks when - // filtered. Do not allow filtering on multiple lists until - // that bug is fixed. - - // Disable filter altogether - // listView.setTextFilterEnabled(!mMultiple); - if (mMultiple) { - listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); - int length = mSelectedArray.length; - for (int i = 0; i < length; i++) { - listView.setItemChecked(mSelectedArray[i], true); - } - } else { - listView.setOnItemClickListener(new OnItemClickListener() { - public void onItemClick(AdapterView parent, View v, - int position, long id) { - mWebViewCore.sendMessage( - EventHub.SINGLE_LISTBOX_CHOICE, (int)id, 0); - dialog.dismiss(); - } - }); - if (mSelection != -1) { - listView.setSelection(mSelection); - listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); - listView.setItemChecked(mSelection, true); - } - } - dialog.setOnCancelListener(new DialogInterface.OnCancelListener() { - public void onCancel(DialogInterface dialog) { - mWebViewCore.sendMessage( - EventHub.SINGLE_LISTBOX_CHOICE, -2, 0); - } - }); - dialog.show(); - } - } - - /* - * Request a dropdown menu for a listbox with multiple selection. - * - * @param array Labels for the listbox. - * @param enabledArray Which positions are enabled. - * @param selectedArray Which positions are initally selected. - */ - void requestListBox(String[] array, boolean[]enabledArray, int[] - selectedArray) { - mPrivateHandler.post( - new InvokeListBox(array, enabledArray, selectedArray)); - } - - /* - * Request a dropdown menu for a listbox with single selection or a single - * <select> element. - * - * @param array Labels for the listbox. - * @param enabledArray Which positions are enabled. - * @param selection Which position is initally selected. - */ - void requestListBox(String[] array, boolean[]enabledArray, int selection) { - mPrivateHandler.post( - new InvokeListBox(array, enabledArray, selection)); - } - - // called by JNI - private void sendFinalFocus(int frame, int node, int x, int y) { - WebViewCore.FocusData focusData = new WebViewCore.FocusData(); - focusData.mFrame = frame; - focusData.mNode = node; - focusData.mX = x; - focusData.mY = y; - mWebViewCore.sendMessage(EventHub.SET_FINAL_FOCUS, - EventHub.NO_FOCUS_CHANGE_BLOCK, 0, focusData); - } - - // called by JNI - private void setFocusData(int moveGeneration, int buildGeneration, - int frame, int node, int x, int y, boolean ignoreNullFocus) { - mFocusData.mMoveGeneration = moveGeneration; - mFocusData.mBuildGeneration = buildGeneration; - mFocusData.mFrame = frame; - mFocusData.mNode = node; - mFocusData.mX = x; - mFocusData.mY = y; - mFocusData.mIgnoreNullFocus = ignoreNullFocus; - } - - // called by JNI - private void sendKitFocus() { - WebViewCore.FocusData focusData = new WebViewCore.FocusData(mFocusData); - mWebViewCore.sendMessage(EventHub.SET_KIT_FOCUS, focusData); - } - - // called by JNI - private void sendMotionUp(int touchGeneration, int buildGeneration, - int frame, int node, int x, int y, int size, boolean isClick, - boolean retry) { - WebViewCore.TouchUpData touchUpData = new WebViewCore.TouchUpData(); - touchUpData.mMoveGeneration = touchGeneration; - touchUpData.mBuildGeneration = buildGeneration; - touchUpData.mSize = size; - touchUpData.mIsClick = isClick; - touchUpData.mRetry = retry; - mFocusData.mFrame = touchUpData.mFrame = frame; - mFocusData.mNode = touchUpData.mNode = node; - mFocusData.mX = touchUpData.mX = x; - mFocusData.mY = touchUpData.mY = y; - mWebViewCore.sendMessage(EventHub.TOUCH_UP, touchUpData); - } - - - private int getScaledMaxXScroll() { - int width; - if (mHeightCanMeasure == false) { - width = getViewWidth() / 4; - } else { - Rect visRect = new Rect(); - calcOurVisibleRect(visRect); - width = visRect.width() / 2; - } - // FIXME the divisor should be retrieved from somewhere - return viewToContent(width); - } - - private int getScaledMaxYScroll() { - int height; - if (mHeightCanMeasure == false) { - height = getViewHeight() / 4; - } else { - Rect visRect = new Rect(); - calcOurVisibleRect(visRect); - height = visRect.height() / 2; - } - // FIXME the divisor should be retrieved from somewhere - // the closest thing today is hard-coded into ScrollView.java - // (from ScrollView.java, line 363) int maxJump = height/2; - return viewToContent(height); - } - - /** - * Called by JNI to invalidate view - */ - private void viewInvalidate() { - invalidate(); - } - - // return true if the key was handled - private boolean navHandledKey(int keyCode, int count, boolean noScroll - , long time) { - if (mNativeClass == 0) { - return false; - } - mLastFocusTime = time; - mLastFocusBounds = nativeGetFocusRingBounds(); - boolean keyHandled = nativeMoveFocus(keyCode, count, noScroll) == false; - if (LOGV_ENABLED) { - Log.v(LOGTAG, "navHandledKey mLastFocusBounds=" + mLastFocusBounds - + " mLastFocusTime=" + mLastFocusTime - + " handled=" + keyHandled); - } - if (keyHandled == false || mHeightCanMeasure == false) { - return keyHandled; - } - Rect contentFocus = nativeGetFocusRingBounds(); - if (contentFocus.isEmpty()) return keyHandled; - Rect viewFocus = contentToView(contentFocus); - Rect visRect = new Rect(); - calcOurVisibleRect(visRect); - Rect outset = new Rect(visRect); - int maxXScroll = visRect.width() / 2; - int maxYScroll = visRect.height() / 2; - outset.inset(-maxXScroll, -maxYScroll); - if (Rect.intersects(outset, viewFocus) == false) { - return keyHandled; - } - // FIXME: Necessary because ScrollView/ListView do not scroll left/right - int maxH = Math.min(viewFocus.right - visRect.right, maxXScroll); - if (maxH > 0) { - pinScrollBy(maxH, 0, true, 0); - } else { - maxH = Math.max(viewFocus.left - visRect.left, -maxXScroll); - if (maxH < 0) { - pinScrollBy(maxH, 0, true, 0); - } - } - if (mLastFocusBounds.isEmpty()) return keyHandled; - if (mLastFocusBounds.equals(contentFocus)) return keyHandled; - if (LOGV_ENABLED) { - Log.v(LOGTAG, "navHandledKey contentFocus=" + contentFocus); - } - requestRectangleOnScreen(viewFocus); - mUserScroll = true; - return keyHandled; - } - - /** - * Set the background color. It's white by default. Pass - * zero to make the view transparent. - * @param color the ARGB color described by Color.java - */ - public void setBackgroundColor(int color) { - mBackgroundColor = color; - mWebViewCore.sendMessage(EventHub.SET_BACKGROUND_COLOR, color); - } - - public void debugDump() { - nativeDebugDump(); - mWebViewCore.sendMessage(EventHub.DUMP_NAVTREE); - } - - /** - * Update our cache with updatedText. - * @param updatedText The new text to put in our cache. - */ - /* package */ void updateCachedTextfield(String updatedText) { - // Also place our generation number so that when we look at the cache - // we recognize that it is up to date. - nativeUpdateCachedTextfield(updatedText, mTextGeneration); - } - - // Never call this version except by updateCachedTextfield(String) - - // we always want to pass in our generation number. - private native void nativeUpdateCachedTextfield(String updatedText, - int generation); - private native void nativeClearFocus(int x, int y); - private native void nativeCreate(int ptr); - private native void nativeDebugDump(); - private native void nativeDestroy(); - 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(); - private native void nativeInstrumentReport(); - private native void nativeMarkNodeInvalid(int node); - // return true if the page has been scrolled - private native boolean nativeMotionUp(int x, int y, int slop, boolean isClick); - // returns false if it handled the key - private native boolean nativeMoveFocus(int keyCode, int count, - boolean noScroll); - private native void nativeNotifyFocusSet(boolean inEditingMode); - private native void nativeRecomputeFocus(); - // 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 focused, - 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 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/WebViewClient.java b/core/java/android/webkit/WebViewClient.java deleted file mode 100644 index a185779..0000000 --- a/core/java/android/webkit/WebViewClient.java +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.webkit; - -import android.graphics.Bitmap; -import android.net.http.SslError; -import android.os.Message; -import android.view.KeyEvent; - -public class WebViewClient { - - /** - * Give the host application a chance to take over the control when a new - * url is about to be loaded in the current WebView. If WebViewClient is not - * provided, by default WebView will ask Activity Manager to choose the - * proper handler for the url. If WebViewClient is provided, return true - * means the host application handles the url, while return false means the - * current WebView handles the url. - * - * @param view The WebView that is initiating the callback. - * @param url The url to be loaded. - * @return True if the host application wants to leave the current WebView - * and handle the url itself, otherwise return false. - */ - public boolean shouldOverrideUrlLoading(WebView view, String url) { - return false; - } - - /** - * Notify the host application that a page has started loading. This method - * is called once for each main frame load so a page with iframes or - * framesets will call onPageStarted one time for the main frame. This also - * means that onPageStarted will not be called when the contents of an - * embedded frame changes, i.e. clicking a link whose target is an iframe. - * - * @param view The WebView that is initiating the callback. - * @param url The url to be loaded. - * @param favicon The favicon for this page if it already exists in the - * database. - */ - public void onPageStarted(WebView view, String url, Bitmap favicon) { - } - - /** - * Notify the host application that a page has finished loading. This method - * is called only for main frame. When onPageFinished() is called, the - * rendering picture may not be updated yet. To get the notification for the - * new Picture, use {@link WebView.PictureListener#onNewPicture}. - * - * @param view The WebView that is initiating the callback. - * @param url The url of the page. - */ - public void onPageFinished(WebView view, String url) { - } - - /** - * Notify the host application that the WebView will load the resource - * specified by the given url. - * - * @param view The WebView that is initiating the callback. - * @param url The url of the resource the WebView will load. - */ - public void onLoadResource(WebView view, String url) { - } - - /** - * Notify the host application that there have been an excessive number of - * HTTP redirects. As the host application if it would like to continue - * trying to load the resource. The default behavior is to send the cancel - * message. - * - * @param view The WebView that is initiating the callback. - * @param cancelMsg The message to send if the host wants to cancel - * @param continueMsg The message to send if the host wants to continue - */ - public void onTooManyRedirects(WebView view, Message cancelMsg, - Message continueMsg) { - cancelMsg.sendToTarget(); - } - - /** - * Report an error to an activity. These errors come up from WebCore, and - * are network errors. - * - * @param view The WebView that is initiating the callback. - * @param errorCode The HTTP error code. - * @param description A String description. - * @param failingUrl The url that failed. - */ - public void onReceivedError(WebView view, int errorCode, - String description, String failingUrl) { - } - - /** - * As the host application if the browser should resend data as the - * requested page was a result of a POST. The default is to not resend the - * data. - * - * @param view The WebView that is initiating the callback. - * @param dontResend The message to send if the browser should not resend - * @param resend The message to send if the browser should resend data - */ - public void onFormResubmission(WebView view, Message dontResend, - Message resend) { - dontResend.sendToTarget(); - } - - /** - * Notify the host application to update its visited links database. - * - * @param view The WebView that is initiating the callback. - * @param url The url being visited. - * @param isReload True if this url is being reloaded. - */ - public void doUpdateVisitedHistory(WebView view, String url, - boolean isReload) { - } - - /** - * Notify the host application to handle a ssl certificate error request - * (display the error to the user and ask whether to proceed or not). The - * host application has to call either handler.cancel() or handler.proceed() - * as the connection is suspended and waiting for the response. The default - * behavior is to cancel the load. - * - * @param view The WebView that is initiating the callback. - * @param handler An SslErrorHandler object that will handle the user's - * response. - * @param error The SSL error object. - * @hide - hide this because it contains a parameter of type SslError, - * which is located in a hidden package. - */ - public void onReceivedSslError(WebView view, SslErrorHandler handler, - SslError error) { - handler.cancel(); - } - - /** - * Notify the host application to handle an authentication request. The - * default behavior is to cancel the request. - * - * @param view The WebView that is initiating the callback. - * @param handler The HttpAuthHandler that will handle the user's response. - * @param host The host requiring authentication. - * @param realm A description to help store user credentials for future - * visits. - */ - public void onReceivedHttpAuthRequest(WebView view, - HttpAuthHandler handler, String host, String realm) { - handler.cancel(); - } - - /** - * Give the host application a chance to handle the key event synchronously. - * e.g. menu shortcut key events need to be filtered this way. If return - * true, WebView will not handle the key event. If return false, WebView - * will always handle the key event, so none of the super in the view chain - * will see the key event. The default behavior returns false. - * - * @param view The WebView that is initiating the callback. - * @param event The key event. - * @return True if the host application wants to handle the key event - * itself, otherwise return false - */ - public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) { - return false; - } - - /** - * Notify the host application that a key was not handled by the WebView. - * Except system keys, WebView always consumes the keys in the normal flow - * or if shouldOverrideKeyEvent returns true. This is called asynchronously - * from where the key is dispatched. It gives the host application an chance - * to handle the unhandled key events. - * - * @param view The WebView that is initiating the callback. - * @param event The key event. - */ - public void onUnhandledKeyEvent(WebView view, KeyEvent event) { - } - - /** - * Notify the host application that the scale applied to the WebView has - * changed. - * - * @param view he WebView that is initiating the callback. - * @param oldScale The old scale factor - * @param newScale The new scale factor - */ - public void onScaleChanged(WebView view, float oldScale, float newScale) { - } -} diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java deleted file mode 100644 index a7261c5..0000000 --- a/core/java/android/webkit/WebViewCore.java +++ /dev/null @@ -1,1672 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.webkit; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.DrawFilter; -import android.graphics.Paint; -import android.graphics.PaintFlagsDrawFilter; -import android.graphics.Picture; -import android.graphics.Point; -import android.graphics.Rect; -import android.graphics.Region; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.os.Process; -import android.util.Config; -import android.util.Log; -import android.util.SparseBooleanArray; -import android.view.KeyEvent; - -import java.util.ArrayList; -import java.util.HashMap; - -import junit.framework.Assert; - -final class WebViewCore { - - private static final String LOGTAG = "webcore"; - static final boolean DEBUG = false; - static final boolean LOGV_ENABLED = DEBUG ? Config.LOGD : Config.LOGV; - - static { - // Load libwebcore during static initialization. This happens in the - // zygote process so it will be shared read-only across all app - // processes. - System.loadLibrary("webcore"); - } - - /* - * WebViewCore always executes in the same thread as the native webkit. - */ - - // The WebView that corresponds to this WebViewCore. - private WebView mWebView; - // Proxy for handling callbacks from native code - private final CallbackProxy mCallbackProxy; - // Settings object for maintaining all settings - private final WebSettings mSettings; - // Context for initializing the BrowserFrame with the proper assets. - private final Context mContext; - // The pointer to a native view object. - private int mNativeClass; - // The BrowserFrame is an interface to the native Frame component. - private BrowserFrame mBrowserFrame; - - /* - * range is from 200 to 10,000. 0 is a special value means device-width. -1 - * means undefined. - */ - private int mViewportWidth = -1; - - /* - * range is from 200 to 10,000. 0 is a special value means device-height. -1 - * means undefined. - */ - private int mViewportHeight = -1; - - /* - * scale in percent, range is from 1 to 1000. 0 means undefined. - */ - private int mViewportInitialScale = 0; - - /* - * scale in percent, range is from 1 to 1000. 0 means undefined. - */ - private int mViewportMinimumScale = 0; - - /* - * scale in percent, range is from 1 to 1000. 0 means undefined. - */ - private int mViewportMaximumScale = 0; - - private boolean mViewportUserScalable = true; - - private int mRestoredScale = 100; - private int mRestoredX = 0; - private int mRestoredY = 0; - - private int mWebkitScrollX = 0; - private int mWebkitScrollY = 0; - - // The thread name used to identify the WebCore thread and for use in - // debugging other classes that require operation within the WebCore thread. - /* package */ static final String THREAD_NAME = "WebViewCoreThread"; - - public WebViewCore(Context context, WebView w, CallbackProxy proxy) { - // No need to assign this in the WebCore thread. - mCallbackProxy = proxy; - mWebView = w; - // This context object is used to initialize the WebViewCore during - // subwindow creation. - mContext = context; - - // We need to wait for the initial thread creation before sending - // a message to the WebCore thread. - // XXX: This is the only time the UI thread will wait for the WebCore - // thread! - synchronized (WebViewCore.class) { - if (sWebCoreHandler == null) { - // Create a global thread and start it. - Thread t = new Thread(new WebCoreThread()); - t.setName(THREAD_NAME); - t.start(); - try { - WebViewCore.class.wait(); - } catch (InterruptedException e) { - Log.e(LOGTAG, "Caught exception while waiting for thread " + - "creation."); - Log.e(LOGTAG, Log.getStackTraceString(e)); - } - } - } - // Create an EventHub to handle messages before and after the thread is - // ready. - mEventHub = new EventHub(); - // Create a WebSettings object for maintaining all settings - mSettings = new WebSettings(mContext); - // The WebIconDatabase needs to be initialized within the UI thread so - // just request the instance here. - WebIconDatabase.getInstance(); - // Send a message to initialize the WebViewCore. - Message init = sWebCoreHandler.obtainMessage( - WebCoreThread.INITIALIZE, this); - sWebCoreHandler.sendMessage(init); - } - - /* Initialize private data within the WebCore thread. - */ - private void initialize() { - /* Initialize our private BrowserFrame class to handle all - * frame-related functions. We need to create a new view which - * in turn creates a C level FrameView and attaches it to the frame. - */ - mBrowserFrame = new BrowserFrame(mContext, this, mCallbackProxy, - mSettings); - // Sync the native settings and also create the WebCore thread handler. - mSettings.syncSettingsAndCreateHandler(mBrowserFrame); - // Create the handler and transfer messages for the IconDatabase - WebIconDatabase.getInstance().createHandler(); - // The transferMessages call will transfer all pending messages to the - // WebCore thread handler. - mEventHub.transferMessages(); - - // Send a message back to WebView to tell it that we have set up the - // WebCore thread. - if (mWebView != null) { - Message.obtain(mWebView.mPrivateHandler, - WebView.WEBCORE_INITIALIZED_MSG_ID, - mNativeClass, 0).sendToTarget(); - } - - } - - /* Handle the initialization of WebViewCore during subwindow creation. This - * method is called from the WebCore thread but it is called before the - * INITIALIZE message can be handled. - */ - /* package */ void initializeSubwindow() { - // Go ahead and initialize the core components. - initialize(); - // Remove the INITIALIZE method so we don't try to initialize twice. - sWebCoreHandler.removeMessages(WebCoreThread.INITIALIZE, this); - } - - /* 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; - } - - //------------------------------------------------------------------------- - // Common methods - //------------------------------------------------------------------------- - - /** - * Causes all timers to pause. This applies to all WebViews in the current - * app process. - */ - public static void pauseTimers() { - if (BrowserFrame.sJavaBridge == null) { - throw new IllegalStateException( - "No WebView has been created in this process!"); - } - BrowserFrame.sJavaBridge.pause(); - } - - /** - * Resume all timers. This applies to all WebViews in the current process. - */ - public static void resumeTimers() { - if (BrowserFrame.sJavaBridge == null) { - throw new IllegalStateException( - "No WebView has been created in this process!"); - } - BrowserFrame.sJavaBridge.resume(); - } - - public WebSettings getSettings() { - return mSettings; - } - - /** - * Invoke a javascript alert. - * @param message The message displayed in the alert. - */ - protected void jsAlert(String url, String message) { - mCallbackProxy.onJsAlert(url, message); - } - - /** - * Invoke a javascript confirm dialog. - * @param message The message displayed in the dialog. - * @return True if the user confirmed or false if the user cancelled. - */ - protected boolean jsConfirm(String url, String message) { - return mCallbackProxy.onJsConfirm(url, message); - } - - /** - * Invoke a javascript prompt dialog. - * @param message The message to be displayed in the dialog. - * @param defaultValue The default value in the prompt input. - * @return The input from the user or null to indicate the user cancelled - * the dialog. - */ - protected String jsPrompt(String url, String message, String defaultValue) { - return mCallbackProxy.onJsPrompt(url, message, defaultValue); - } - - /** - * Invoke a javascript before unload dialog. - * @param url The url that is requesting the dialog. - * @param message The message displayed in the dialog. - * @return True if the user confirmed or false if the user cancelled. False - * will cancel the navigation. - */ - protected boolean jsUnload(String url, String message) { - return mCallbackProxy.onJsBeforeUnload(url, message); - } - - //------------------------------------------------------------------------- - // JNI methods - //------------------------------------------------------------------------- - - static native String nativeFindAddress(String addr); - - /** - * Rebuild the nav cache if the dom changed. - */ - private native void nativeCheckNavCache(); - - /** - * Empty the picture set. - */ - 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); - - /** - * Redraw a portion of the picture set. The Point wh returns the - * width and height of the overall picture. - */ - private native boolean nativeRecordContent(Region invalRegion, Point wh); - - /** - * Splits slow parts of the picture set. Called from the webkit - * thread after nativeDrawContent returns true. - */ - private native void nativeSplitContent(); - - private native boolean nativeKey(int keyCode, int unichar, - int repeatCount, boolean isShift, boolean isAlt, boolean isDown); - - private native boolean nativeClick(); - - private native void nativeSendListBoxChoices(boolean[] choices, int size); - - private native void nativeSendListBoxChoice(int choice); - - /* Tell webkit what its width and height are, for the purposes - of layout/line-breaking. These coordinates are in document space, - which is the same as View coords unless we have zoomed the document - (see nativeSetZoom). - screenWidth is used by layout to wrap column around. If viewport uses - fixed size, screenWidth can be different from width with zooming. - should this be called nativeSetViewPortSize? - */ - private native void nativeSetSize(int width, int height, int screenWidth, - float scale, int realScreenWidth, int screenHeight); - - private native int nativeGetContentMinPrefWidth(); - - // Start: functions that deal with text editing - private native void nativeReplaceTextfieldText(int frame, int node, int x, - int y, int oldStart, int oldEnd, String replace, int newStart, - int newEnd); - - private native void passToJs(int frame, int node, int x, int y, int gen, - String currentText, int keyCode, int keyValue, boolean down, - boolean cap, boolean fn, boolean sym); - - private native void nativeSaveDocumentState(int frame); - - private native void nativeSetFinalFocus(int framePtr, int nodePtr, int x, - int y, boolean block); - - private native void nativeSetKitFocus(int moveGeneration, - int buildGeneration, int framePtr, int nodePtr, int x, int y, - boolean ignoreNullFocus); - - private native String nativeRetrieveHref(int framePtr, int nodePtr); - - 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(); - - private native void nativeSetSnapAnchor(int x, int y); - - private native void nativeSnapToAnchor(); - - private native void nativeSetBackgroundColor(int color); - - private native void nativeDumpDomTree(boolean useFile); - - private native void nativeDumpRenderTree(boolean useFile); - - private native void nativeDumpNavTree(); - - private native void nativeRefreshPlugins(boolean reloadOpenPages); - - /** - * Delete text from start to end in the focused textfield. If there is no - * focus, or if start == end, silently fail. If start and end are out of - * order, swap them. - * @param start Beginning of selection to delete. - * @param end End of selection to delete. - */ - private native void nativeDeleteSelection(int frame, int node, int x, int y, - int start, int end); - - /** - * Set the selection to (start, end) in the focused textfield. If start and - * end are out of order, swap them. - * @param start Beginning of selection. - * @param end End of selection. - */ - private native void nativeSetSelection(int frame, int node, int x, int y, - int start, int end); - - 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 - private static Handler sWebCoreHandler; - // Class for providing Handler creation inside the WebCore thread. - private static class WebCoreThread implements Runnable { - // Message id for initializing a new WebViewCore. - private static final int INITIALIZE = 0; - private static final int REDUCE_PRIORITY = 1; - private static final int RESUME_PRIORITY = 2; - private static final int CACHE_TICKER = 3; - private static final int BLOCK_CACHE_TICKER = 4; - private static final int RESUME_CACHE_TICKER = 5; - - private static final int CACHE_TICKER_INTERVAL = 60 * 1000; // 1 minute - - private static boolean mCacheTickersBlocked = true; - - public void run() { - Looper.prepare(); - Assert.assertNull(sWebCoreHandler); - synchronized (WebViewCore.class) { - sWebCoreHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case INITIALIZE: - WebViewCore core = (WebViewCore) msg.obj; - core.initialize(); - break; - - case REDUCE_PRIORITY: - // 3 is an adjustable number. - Process.setThreadPriority( - Process.THREAD_PRIORITY_DEFAULT + 3 * - Process.THREAD_PRIORITY_LESS_FAVORABLE); - break; - - case RESUME_PRIORITY: - Process.setThreadPriority( - Process.THREAD_PRIORITY_DEFAULT); - break; - - case CACHE_TICKER: - if (!mCacheTickersBlocked) { - CacheManager.endCacheTransaction(); - CacheManager.startCacheTransaction(); - sendMessageDelayed( - obtainMessage(CACHE_TICKER), - CACHE_TICKER_INTERVAL); - } - break; - - case BLOCK_CACHE_TICKER: - if (CacheManager.endCacheTransaction()) { - mCacheTickersBlocked = true; - } - break; - - case RESUME_CACHE_TICKER: - if (CacheManager.startCacheTransaction()) { - mCacheTickersBlocked = false; - } - break; - } - } - }; - WebViewCore.class.notify(); - } - Looper.loop(); - } - } - - static class FocusData { - FocusData() {} - FocusData(FocusData d) { - mMoveGeneration = d.mMoveGeneration; - mBuildGeneration = d.mBuildGeneration; - mFrame = d.mFrame; - mNode = d.mNode; - mX = d.mX; - mY = d.mY; - mIgnoreNullFocus = d.mIgnoreNullFocus; - } - int mMoveGeneration; - int mBuildGeneration; - int mFrame; - int mNode; - int mX; - int mY; - boolean mIgnoreNullFocus; - } - - static class TouchUpData { - int mMoveGeneration; - int mBuildGeneration; - int mFrame; - int mNode; - int mX; - int mY; - int mSize; - boolean mIsClick; - boolean mRetry; - } - - static class TouchEventData { - int mAction; // MotionEvent.getAction() - int mX; - int mY; - } - - static final String[] HandlerDebugString = { - "LOAD_URL", // = 100; - "STOP_LOADING", // = 101; - "RELOAD", // = 102; - "KEY_DOWN", // = 103; - "KEY_UP", // = 104; - "VIEW_SIZE_CHANGED", // = 105; - "GO_BACK_FORWARD", // = 106; - "SET_SCROLL_OFFSET", // = 107; - "RESTORE_STATE", // = 108; - "PAUSE_TIMERS", // = 109; - "RESUME_TIMERS", // = 110; - "CLEAR_CACHE", // = 111; - "CLEAR_HISTORY", // = 112; - "SET_SELECTION", // = 113; - "REPLACE_TEXT", // = 114; - "PASS_TO_JS", // = 115; - "SET_GLOBAL_BOUNDS", // = 116; - "UPDATE_CACHE_AND_TEXT_ENTRY", // = 117; - "CLICK", // = 118; - "119", - "DOC_HAS_IMAGES", // = 120; - "SET_SNAP_ANCHOR", // = 121; - "DELETE_SELECTION", // = 122; - "LISTBOX_CHOICES", // = 123; - "SINGLE_LISTBOX_CHOICE", // = 124; - "125", - "SET_BACKGROUND_COLOR", // = 126; - "UNBLOCK_FOCUS", // = 127; - "SAVE_DOCUMENT_STATE", // = 128; - "GET_SELECTION", // = 129; - "WEBKIT_DRAW", // = 130; - "SYNC_SCROLL", // = 131; - "REFRESH_PLUGINS", // = 132; - "SPLIT_PICTURE_SET", // = 133; - "CLEAR_CONTENT", // = 134; - "SET_FINAL_FOCUS", // = 135; - "SET_KIT_FOCUS", // = 136; - "REQUEST_FOCUS_HREF", // = 137; - "ADD_JS_INTERFACE", // = 138; - "LOAD_DATA", // = 139; - "TOUCH_UP", // = 140; - "TOUCH_EVENT", // = 141; - }; - - class EventHub { - // Message Ids - static final int LOAD_URL = 100; - static final int STOP_LOADING = 101; - static final int RELOAD = 102; - static final int KEY_DOWN = 103; - static final int KEY_UP = 104; - static final int VIEW_SIZE_CHANGED = 105; - static final int GO_BACK_FORWARD = 106; - 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; - static final int CLEAR_CACHE = 111; - static final int CLEAR_HISTORY = 112; - static final int SET_SELECTION = 113; - static final int REPLACE_TEXT = 114; - static final int PASS_TO_JS = 115; - static final int SET_GLOBAL_BOUNDS = 116; - static final int UPDATE_CACHE_AND_TEXT_ENTRY = 117; - static final int CLICK = 118; - 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 SET_BACKGROUND_COLOR = 126; - static final int UNBLOCK_FOCUS = 127; - static final int SAVE_DOCUMENT_STATE = 128; - static final int GET_SELECTION = 129; - 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 SET_FINAL_FOCUS = 135; - static final int SET_KIT_FOCUS = 136; - static final int REQUEST_FOCUS_HREF = 137; - static final int ADD_JS_INTERFACE = 138; - static final int LOAD_DATA = 139; - - // 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; - - // 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; - - // flag values passed to message SET_FINAL_FOCUS - static final int NO_FOCUS_CHANGE_BLOCK = 0; - static final int BLOCK_FOCUS_CHANGE_UNTIL_KEY_UP = 1; - - // Private handler for WebCore messages. - private Handler mHandler; - // Message queue for containing messages before the WebCore thread is - // ready. - private ArrayList<Message> mMessages = new ArrayList<Message>(); - // Flag for blocking messages. This is used during DESTROY to avoid - // posting more messages to the EventHub or to WebView's event handler. - private boolean mBlockMessages; - - private int mTid; - private int mSavedPriority; - - /** - * Prevent other classes from creating an EventHub. - */ - private EventHub() {} - - /** - * Transfer all messages to the newly created webcore thread handler. - */ - private void transferMessages() { - mTid = Process.myTid(); - mSavedPriority = Process.getThreadPriority(mTid); - - mHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - if (LOGV_ENABLED) { - Log.v(LOGTAG, msg.what < LOAD_URL || msg.what - > TOUCH_EVENT ? Integer.toString(msg.what) - : HandlerDebugString[msg.what - LOAD_URL]); - } - switch (msg.what) { - case WEBKIT_DRAW: - webkitDraw(); - break; - - case DESTROY: - // Time to take down the world. Cancel all pending - // loads and destroy the native view and frame. - mBrowserFrame.destroy(); - mBrowserFrame = null; - mNativeClass = 0; - break; - - case LOAD_URL: - loadUrl((String) msg.obj); - break; - - case LOAD_DATA: - HashMap loadParams = (HashMap) msg.obj; - 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"), - (String) loadParams.get("failUrl")); - break; - - case STOP_LOADING: - // If the WebCore has committed the load, but not - // finished the first layout yet, we need to set - // first layout done to trigger the interpreted side sync - // up with native side - if (mBrowserFrame.committed() - && !mBrowserFrame.firstLayoutDone()) { - mBrowserFrame.didFirstLayout(); - } - // Do this after syncing up the layout state. - stopLoading(); - break; - - case RELOAD: - mBrowserFrame.reload(false); - break; - - case KEY_DOWN: - key((KeyEvent) msg.obj, true); - break; - - case KEY_UP: - key((KeyEvent) msg.obj, false); - break; - - case CLICK: - nativeClick(); - break; - - case VIEW_SIZE_CHANGED: - viewSizeChanged(msg.arg1, msg.arg2, - ((Integer) msg.obj).intValue()); - break; - - case SET_SCROLL_OFFSET: - // note: these are in document coordinates - // (inv-zoom) - 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: - // If it is a standard load and the load is not - // committed yet, we interpret BACK as RELOAD - if (!mBrowserFrame.committed() && msg.arg1 == -1 && - (mBrowserFrame.loadType() == - BrowserFrame.FRAME_LOADTYPE_STANDARD)) { - mBrowserFrame.reload(true); - } else { - mBrowserFrame.goBackOrForward(msg.arg1); - } - break; - - case RESTORE_STATE: - stopLoading(); - restoreState(msg.arg1); - break; - - case PAUSE_TIMERS: - mSavedPriority = Process.getThreadPriority(mTid); - Process.setThreadPriority(mTid, - Process.THREAD_PRIORITY_BACKGROUND); - pauseTimers(); - if (CacheManager.disableTransaction()) { - WebCoreThread.mCacheTickersBlocked = true; - sWebCoreHandler.removeMessages( - WebCoreThread.CACHE_TICKER); - } - break; - - case RESUME_TIMERS: - Process.setThreadPriority(mTid, mSavedPriority); - resumeTimers(); - if (CacheManager.enableTransaction()) { - WebCoreThread.mCacheTickersBlocked = false; - sWebCoreHandler.sendMessageDelayed( - sWebCoreHandler.obtainMessage( - WebCoreThread.CACHE_TICKER), - WebCoreThread.CACHE_TICKER_INTERVAL); - } - break; - - case CLEAR_CACHE: - mBrowserFrame.clearCache(); - if (msg.arg1 == 1) { - CacheManager.removeAllCacheFiles(); - } - break; - - case CLEAR_HISTORY: - mCallbackProxy.getBackForwardList(). - close(mBrowserFrame.mNativeFrame); - break; - - case REPLACE_TEXT: - HashMap jMap = (HashMap) msg.obj; - FocusData fData = (FocusData) jMap.get("focusData"); - String replace = (String) jMap.get("replace"); - int newStart = - ((Integer) jMap.get("start")).intValue(); - int newEnd = - ((Integer) jMap.get("end")).intValue(); - nativeReplaceTextfieldText(fData.mFrame, - fData.mNode, fData.mX, fData.mY, msg.arg1, - msg.arg2, replace, newStart, newEnd); - break; - - case PASS_TO_JS: { - HashMap jsMap = (HashMap) msg.obj; - FocusData fDat = (FocusData) jsMap.get("focusData"); - KeyEvent evt = (KeyEvent) jsMap.get("event"); - int keyCode = evt.getKeyCode(); - int keyValue = evt.getUnicodeChar(); - int generation = msg.arg1; - passToJs(fDat.mFrame, fDat.mNode, fDat.mX, fDat.mY, - generation, - (String) jsMap.get("currentText"), - keyCode, - keyValue, - evt.isDown(), - evt.isShiftPressed(), evt.isAltPressed(), - evt.isSymPressed()); - break; - } - - case SAVE_DOCUMENT_STATE: { - FocusData fDat = (FocusData) msg.obj; - nativeSaveDocumentState(fDat.mFrame); - break; - } - - case CLEAR_SSL_PREF_TABLE: - Network.getInstance(mContext) - .clearUserSslPrefTable(); - break; - - case TOUCH_UP: - TouchUpData touchUpData = (TouchUpData) msg.obj; - nativeTouchUp(touchUpData.mMoveGeneration, - touchUpData.mBuildGeneration, - touchUpData.mFrame, touchUpData.mNode, - touchUpData.mX, touchUpData.mY, - touchUpData.mSize, touchUpData.mIsClick, - touchUpData.mRetry); - break; - - case TOUCH_EVENT: { - TouchEventData ted = (TouchEventData) msg.obj; - Message.obtain( - mWebView.mPrivateHandler, - WebView.PREVENT_TOUCH_ID, ted.mAction, - nativeHandleTouchEvent(ted.mAction, ted.mX, - ted.mY) ? 1 : 0).sendToTarget(); - break; - } - - case ADD_JS_INTERFACE: - HashMap map = (HashMap) msg.obj; - Object obj = map.get("object"); - String interfaceName = (String) - map.get("interfaceName"); - mBrowserFrame.addJavascriptInterface(obj, - interfaceName); - break; - - case REQUEST_EXT_REPRESENTATION: - mBrowserFrame.externalRepresentation( - (Message) msg.obj); - break; - - case REQUEST_DOC_AS_TEXT: - mBrowserFrame.documentAsText((Message) msg.obj); - break; - - case SET_FINAL_FOCUS: - FocusData finalData = (FocusData) msg.obj; - nativeSetFinalFocus(finalData.mFrame, - finalData.mNode, finalData.mX, - finalData.mY, msg.arg1 - != EventHub.NO_FOCUS_CHANGE_BLOCK); - break; - - case UNBLOCK_FOCUS: - nativeUnblockFocus(); - break; - - case SET_KIT_FOCUS: - FocusData focusData = (FocusData) msg.obj; - nativeSetKitFocus(focusData.mMoveGeneration, - focusData.mBuildGeneration, - focusData.mFrame, focusData.mNode, - focusData.mX, focusData.mY, - focusData.mIgnoreNullFocus); - break; - - case REQUEST_FOCUS_HREF: { - Message hrefMsg = (Message) msg.obj; - String res = nativeRetrieveHref(msg.arg1, msg.arg2); - hrefMsg.getData().putString("url", res); - hrefMsg.sendToTarget(); - break; - } - - case UPDATE_CACHE_AND_TEXT_ENTRY: - nativeUpdateFrameCache(); - // FIXME: this should provide a minimal rectangle - if (mWebView != null) { - mWebView.postInvalidate(); - } - sendUpdateTextEntry(); - break; - - case DOC_HAS_IMAGES: - Message imageResult = (Message) msg.obj; - imageResult.arg1 = - mBrowserFrame.documentHasImages() ? 1 : 0; - imageResult.sendToTarget(); - break; - - case SET_SNAP_ANCHOR: - nativeSetSnapAnchor(msg.arg1, msg.arg2); - break; - - case DELETE_SELECTION: - FocusData delData = (FocusData) msg.obj; - nativeDeleteSelection(delData.mFrame, - delData.mNode, delData.mX, - delData.mY, msg.arg1, msg.arg2); - break; - - case SET_SELECTION: - FocusData selData = (FocusData) msg.obj; - nativeSetSelection(selData.mFrame, - selData.mNode, selData.mX, - selData.mY, msg.arg1, msg.arg2); - break; - - case LISTBOX_CHOICES: - SparseBooleanArray choices = (SparseBooleanArray) - msg.obj; - int choicesSize = msg.arg1; - boolean[] choicesArray = new boolean[choicesSize]; - for (int c = 0; c < choicesSize; c++) { - choicesArray[c] = choices.get(c); - } - nativeSendListBoxChoices(choicesArray, - choicesSize); - break; - - case SINGLE_LISTBOX_CHOICE: - nativeSendListBoxChoice(msg.arg1); - break; - - case SET_BACKGROUND_COLOR: - nativeSetBackgroundColor(msg.arg1); - break; - - case GET_SELECTION: - String str = nativeGetSelection((Region) msg.obj); - Message.obtain(mWebView.mPrivateHandler - , WebView.UPDATE_CLIPBOARD, str) - .sendToTarget(); - break; - - case DUMP_DOMTREE: - nativeDumpDomTree(msg.arg1 == 1); - break; - - case DUMP_RENDERTREE: - nativeDumpRenderTree(msg.arg1 == 1); - break; - - case DUMP_NAVTREE: - nativeDumpNavTree(); - break; - - case SYNC_SCROLL: - mWebkitScrollX = msg.arg1; - mWebkitScrollY = msg.arg2; - break; - - 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; - } - } - }; - // Take all queued messages and resend them to the new handler. - synchronized (this) { - int size = mMessages.size(); - for (int i = 0; i < size; i++) { - mHandler.sendMessage(mMessages.get(i)); - } - mMessages = null; - } - } - - /** - * Send a message internally to the queue or to the handler - */ - private synchronized void sendMessage(Message msg) { - if (mBlockMessages) { - return; - } - if (mMessages != null) { - mMessages.add(msg); - } else { - mHandler.sendMessage(msg); - } - } - - private synchronized void removeMessages(int what) { - if (mBlockMessages) { - return; - } - if (what == EventHub.WEBKIT_DRAW) { - mDrawIsScheduled = false; - } - if (mMessages != null) { - Log.w(LOGTAG, "Not supported in this case."); - } else { - mHandler.removeMessages(what); - } - } - - private synchronized void sendMessageDelayed(Message msg, long delay) { - if (mBlockMessages) { - return; - } - mHandler.sendMessageDelayed(msg, delay); - } - - /** - * Send a message internally to the front of the queue. - */ - private synchronized void sendMessageAtFrontOfQueue(Message msg) { - if (mBlockMessages) { - return; - } - if (mMessages != null) { - mMessages.add(0, msg); - } else { - mHandler.sendMessageAtFrontOfQueue(msg); - } - } - - /** - * Remove all the messages. - */ - private synchronized void removeMessages() { - // reset mDrawIsScheduled flag as WEBKIT_DRAW may be removed - mDrawIsScheduled = false; - mSplitPictureIsScheduled = false; - if (mMessages != null) { - mMessages.clear(); - } else { - mHandler.removeCallbacksAndMessages(null); - } - } - - /** - * Block sending messages to the EventHub. - */ - private synchronized void blockMessages() { - mBlockMessages = true; - } - } - - //------------------------------------------------------------------------- - // Methods called by host activity (in the same thread) - //------------------------------------------------------------------------- - - void stopLoading() { - if (LOGV_ENABLED) Log.v(LOGTAG, "CORE stopLoading"); - if (mBrowserFrame != null) { - mBrowserFrame.stopLoading(); - } - } - - //------------------------------------------------------------------------- - // Methods called by WebView - // If it refers to local variable, it needs synchronized(). - // If it needs WebCore, it has to send message. - //------------------------------------------------------------------------- - - void sendMessage(Message msg) { - mEventHub.sendMessage(msg); - } - - void sendMessage(int what) { - mEventHub.sendMessage(Message.obtain(null, what)); - } - - void sendMessage(int what, Object obj) { - mEventHub.sendMessage(Message.obtain(null, what, obj)); - } - - void sendMessage(int what, int arg1) { - // just ignore the second argument (make it 0) - mEventHub.sendMessage(Message.obtain(null, what, arg1, 0)); - } - - void sendMessage(int what, int arg1, int arg2) { - mEventHub.sendMessage(Message.obtain(null, what, arg1, arg2)); - } - - void sendMessage(int what, int arg1, Object obj) { - // just ignore the second argument (make it 0) - mEventHub.sendMessage(Message.obtain(null, what, arg1, 0, obj)); - } - - void sendMessage(int what, int arg1, int arg2, Object obj) { - mEventHub.sendMessage(Message.obtain(null, what, arg1, arg2, obj)); - } - - void sendMessageDelayed(int what, Object obj, long delay) { - mEventHub.sendMessageDelayed(Message.obtain(null, what, obj), delay); - } - - void removeMessages(int what) { - mEventHub.removeMessages(what); - } - - void removeMessages() { - mEventHub.removeMessages(); - } - - /** - * Removes pending messages and trigger a DESTROY message to send to - * WebCore. - * Called from UI thread. - */ - void destroy() { - // We don't want anyone to post a message between removing pending - // messages and sending the destroy message. - synchronized (mEventHub) { - mEventHub.removeMessages(); - mEventHub.sendMessageAtFrontOfQueue( - Message.obtain(null, EventHub.DESTROY)); - mEventHub.blockMessages(); - mWebView = null; - } - } - - //------------------------------------------------------------------------- - // WebViewCore private methods - //------------------------------------------------------------------------- - - private void loadUrl(String url) { - if (LOGV_ENABLED) Log.v(LOGTAG, " CORE loadUrl " + url); - mBrowserFrame.loadUrl(url); - } - - private void key(KeyEvent evt, boolean isDown) { - if (LOGV_ENABLED) { - Log.v(LOGTAG, "CORE key at " + System.currentTimeMillis() + ", " - + evt); - } - if (!nativeKey(evt.getKeyCode(), evt.getUnicodeChar(), - evt.getRepeatCount(), evt.isShiftPressed(), evt.isAltPressed(), - isDown)) { - // bubble up the event handling - mCallbackProxy.onUnhandledKeyEvent(evt); - } - } - - // These values are used to avoid requesting a layout based on old values - private int mCurrentViewWidth = 0; - private int mCurrentViewHeight = 0; - - // Define a minimum screen width so that we won't wrap the paragraph to one - // word per line during zoom-in. - private static final int MIN_SCREEN_WIDTH = 160; - - // notify webkit that our virtual view size changed size (after inv-zoom) - private void viewSizeChanged(int w, int h, int viewWidth) { - if (LOGV_ENABLED) Log.v(LOGTAG, "CORE onSizeChanged"); - if (w == 0) { - Log.w(LOGTAG, "skip viewSizeChanged as w is 0"); - return; - } - // negative scale indicate that WebCore should reuse the current scale - float scale = (float) viewWidth / w; - if (mSettings.getUseWideViewPort() - && (w < mViewportWidth || mViewportWidth == -1)) { - int width = mViewportWidth; - int screenWidth = Math.max(w, MIN_SCREEN_WIDTH); - if (mViewportWidth == -1) { - if (mSettings.getLayoutAlgorithm() == - WebSettings.LayoutAlgorithm.NORMAL) { - width = WebView.ZOOM_OUT_WIDTH; - } else { - /* - * if a page's minimum preferred width is wider than the - * given "w", use it instead to get better layout result. If - * we start a page with MAX_ZOOM_WIDTH, "w" will be always - * wider. If we start a page with screen width, due to the - * delay between {@link #didFirstLayout} and - * {@link #viewSizeChanged}, - * {@link #nativeGetContentMinPrefWidth} will return a more - * accurate value than initial 0 to result a better layout. - * In the worse case, the native width will be adjusted when - * next zoom or screen orientation change happens. - */ - int minContentWidth = nativeGetContentMinPrefWidth(); - if (minContentWidth > WebView.MAX_FLOAT_CONTENT_WIDTH) { - // keep the same width and screen width so that there is - // no reflow when zoom-out - width = minContentWidth; - screenWidth = Math.min(screenWidth, Math.abs(viewWidth)); - } else { - width = Math.max(w, minContentWidth); - } - } - } - nativeSetSize(width, Math.round((float) width * h / w), - screenWidth, scale, w, h); - } else { - nativeSetSize(w, h, w, scale, w, h); - } - // Remember the current width and height - boolean needInvalidate = (mCurrentViewWidth == 0); - mCurrentViewWidth = w; - mCurrentViewHeight = h; - if (needInvalidate) { - // ensure {@link #webkitDraw} is called as we were blocking in - // {@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)); - } - - private void sendUpdateTextEntry() { - if (mWebView != null) { - Message.obtain(mWebView.mPrivateHandler, - WebView.UPDATE_TEXT_ENTRY_MSG_ID).sendToTarget(); - } - } - - // 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; - 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, nativeGetContentMinPrefWidth(), - 0, draw).sendToTarget(); - nativeCheckNavCache(); - if (mWebkitScrollX != 0 || mWebkitScrollY != 0) { - // as we have the new picture, try to sync the scroll position - Message.obtain(mWebView.mPrivateHandler, - WebView.SYNC_SCROLL_TO_MSG_ID, mWebkitScrollX, - mWebkitScrollY).sendToTarget(); - mWebkitScrollX = mWebkitScrollY = 0; - } - // nativeSnapToAnchor() needs to be called after NEW_PICTURE_MSG_ID - // is sent, so that scroll will be based on the new content size. - nativeSnapToAnchor(); - } - } - - /////////////////////////////////////////////////////////////////////////// - // These are called from the UI thread, not our thread - - static final int ZOOM_BITS = Paint.FILTER_BITMAP_FLAG | - Paint.DITHER_FLAG | - Paint.SUBPIXEL_TEXT_FLAG; - static final int SCROLL_BITS = Paint.FILTER_BITMAP_FLAG | - Paint.DITHER_FLAG; - - final DrawFilter mZoomFilter = - new PaintFlagsDrawFilter(ZOOM_BITS, Paint.LINEAR_TEXT_FLAG); - final DrawFilter mScrollFilter = - new PaintFlagsDrawFilter(SCROLL_BITS, 0); - - /* package */ void drawContentPicture(Canvas canvas, int color, - boolean animatingZoom, - boolean animatingScroll) { - DrawFilter df = null; - if (animatingZoom) { - df = mZoomFilter; - } else if (animatingScroll) { - df = mScrollFilter; - } - canvas.setDrawFilter(df); - boolean tookTooLong = nativeDrawContent(canvas, color); - canvas.setDrawFilter(null); - if (tookTooLong && mSplitPictureIsScheduled == false) { - mSplitPictureIsScheduled = true; - sendMessage(EventHub.SPLIT_PICTURE_SET); - } - } - - /*package*/ Picture copyContentPicture() { - Picture result = new Picture(); - nativeCopyContentToPicture(result); - return result; - } - - static void pauseUpdate(WebViewCore core) { - // remove the pending REDUCE_PRIORITY and RESUME_PRIORITY messages - sWebCoreHandler.removeMessages(WebCoreThread.REDUCE_PRIORITY); - sWebCoreHandler.removeMessages(WebCoreThread.RESUME_PRIORITY); - sWebCoreHandler.sendMessageAtFrontOfQueue(sWebCoreHandler - .obtainMessage(WebCoreThread.REDUCE_PRIORITY)); - // Note: there is one possible failure mode. If pauseUpdate() is called - // from UI thread while in webcore thread WEBKIT_DRAW is just pulled out - // of the queue and about to be executed. mDrawIsScheduled may be set to - // false in webkitDraw(). So update won't be blocked. But at least the - // webcore thread priority is still lowered. - if (core != null) { - synchronized (core) { - core.mDrawIsPaused = true; - core.mEventHub.removeMessages(EventHub.WEBKIT_DRAW); - } - } - } - - static void resumeUpdate(WebViewCore core) { - // remove the pending REDUCE_PRIORITY and RESUME_PRIORITY messages - sWebCoreHandler.removeMessages(WebCoreThread.REDUCE_PRIORITY); - sWebCoreHandler.removeMessages(WebCoreThread.RESUME_PRIORITY); - sWebCoreHandler.sendMessageAtFrontOfQueue(sWebCoreHandler - .obtainMessage(WebCoreThread.RESUME_PRIORITY)); - if (core != null) { - synchronized (core) { - core.mDrawIsScheduled = false; - core.mDrawIsPaused = false; - if (LOGV_ENABLED) Log.v(LOGTAG, "resumeUpdate"); - core.contentDraw(); - } - } - } - - static void startCacheTransaction() { - sWebCoreHandler.sendMessage(sWebCoreHandler - .obtainMessage(WebCoreThread.RESUME_CACHE_TICKER)); - } - - static void endCacheTransaction() { - sWebCoreHandler.sendMessage(sWebCoreHandler - .obtainMessage(WebCoreThread.BLOCK_CACHE_TICKER)); - } - - ////////////////////////////////////////////////////////////////////////// - - private void restoreState(int index) { - WebBackForwardList list = mCallbackProxy.getBackForwardList(); - int size = list.getSize(); - for (int i = 0; i < size; i++) { - list.getItemAtIndex(i).inflate(mBrowserFrame.mNativeFrame); - } - mBrowserFrame.mLoadInitFromJava = true; - list.restoreIndex(mBrowserFrame.mNativeFrame, index); - mBrowserFrame.mLoadInitFromJava = false; - } - - //------------------------------------------------------------------------- - // Implement abstract methods in WebViewCore, native WebKit callback part - //------------------------------------------------------------------------- - - // called from JNI or WebView thread - /* package */ void contentDraw() { - // don't update the Picture until we have an initial width and finish - // the first layout - if (mCurrentViewWidth == 0 || !mBrowserFrame.firstLayoutDone()) { - return; - } - // only fire an event if this is our first request - synchronized (this) { - if (mDrawIsPaused || mDrawIsScheduled) { - return; - } - mDrawIsScheduled = true; - mEventHub.sendMessage(Message.obtain(null, EventHub.WEBKIT_DRAW)); - } - } - - // called by JNI - private void contentScrollBy(int dx, int dy, boolean animate) { - if (!mBrowserFrame.firstLayoutDone()) { - // Will this happen? If yes, we need to do something here. - return; - } - if (mWebView != null) { - Message.obtain(mWebView.mPrivateHandler, - WebView.SCROLL_BY_MSG_ID, dx, dy, - new Boolean(animate)).sendToTarget(); - } - } - - // called by JNI - private void contentScrollTo(int x, int y) { - if (!mBrowserFrame.firstLayoutDone()) { - /* - * WebKit restore state will be called before didFirstLayout(), - * remember the position as it has to be applied after restoring - * zoom factor which is controlled by screenWidth. - */ - mRestoredX = x; - mRestoredY = y; - return; - } - if (mWebView != null) { - Message.obtain(mWebView.mPrivateHandler, - WebView.SCROLL_TO_MSG_ID, x, y).sendToTarget(); - } - } - - // called by JNI - private void contentSpawnScrollTo(int x, int y) { - if (!mBrowserFrame.firstLayoutDone()) { - /* - * WebKit restore state will be called before didFirstLayout(), - * remember the position as it has to be applied after restoring - * zoom factor which is controlled by screenWidth. - */ - mRestoredX = x; - mRestoredY = y; - return; - } - if (mWebView != null) { - Message.obtain(mWebView.mPrivateHandler, - WebView.SPAWN_SCROLL_TO_MSG_ID, x, y).sendToTarget(); - } - } - - // called by JNI - private void sendMarkNodeInvalid(int node) { - if (mWebView != null) { - Message.obtain(mWebView.mPrivateHandler, - WebView.MARK_NODE_INVALID_ID, node, 0).sendToTarget(); - } - } - - // called by JNI - private void sendNotifyFocusSet() { - if (mWebView != null) { - Message.obtain(mWebView.mPrivateHandler, - WebView.NOTIFY_FOCUS_SET_MSG_ID).sendToTarget(); - } - } - - // called by JNI - private void sendNotifyProgressFinished() { - sendUpdateTextEntry(); - // as CacheManager can behave based on database transaction, we need to - // call tick() to trigger endTransaction - sWebCoreHandler.removeMessages(WebCoreThread.CACHE_TICKER); - sWebCoreHandler.sendMessage(sWebCoreHandler - .obtainMessage(WebCoreThread.CACHE_TICKER)); - contentDraw(); - } - - // called by JNI - private void sendRecomputeFocus() { - if (mWebView != null) { - Message.obtain(mWebView.mPrivateHandler, - WebView.RECOMPUTE_FOCUS_MSG_ID).sendToTarget(); - } - } - - /* Called by JNI. The coordinates are in doc coordinates, so they need to - be scaled before they can be used by the view system, which happens - in WebView since it (and its thread) know the current scale factor. - */ - private void sendViewInvalidate(int left, int top, int right, int bottom) { - if (mWebView != null) { - Message.obtain(mWebView.mPrivateHandler, - WebView.INVAL_RECT_MSG_ID, - new Rect(left, top, right, bottom)).sendToTarget(); - } - } - - /* package */ WebView getWebView() { - return mWebView; - } - - private native void setViewportSettingsFromNative(); - - // called by JNI - 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 - // white space in the GMail which uses WebView for message view. - if (mWebView != null && mWebView.mHeightCanMeasure) { - mWebView.mLastHeightSent = 0; - // Send a negative screen width to indicate that WebCore should - // reuse the current scale - mEventHub.sendMessage(Message.obtain(null, - EventHub.VIEW_SIZE_CHANGED, mWebView.mLastWidthSent, - mWebView.mLastHeightSent, -mWebView.mLastWidthSent)); - } - - mBrowserFrame.didFirstLayout(); - - // reset the scroll position as it is a new page now - mWebkitScrollX = mWebkitScrollY = 0; - - // set the viewport settings from WebKit - setViewportSettingsFromNative(); - - // infer the values if they are not defined. - if (mViewportWidth == 0) { - if (mViewportInitialScale == 0) { - mViewportInitialScale = 100; - } - if (mViewportMinimumScale == 0) { - mViewportMinimumScale = 100; - } - } - if (mViewportUserScalable == false) { - mViewportInitialScale = 100; - mViewportMinimumScale = 100; - mViewportMaximumScale = 100; - } - if (mViewportMinimumScale > mViewportInitialScale) { - if (mViewportInitialScale == 0) { - mViewportInitialScale = mViewportMinimumScale; - } else { - mViewportMinimumScale = mViewportInitialScale; - } - } - if (mViewportMaximumScale > 0) { - if (mViewportMaximumScale < mViewportInitialScale) { - mViewportMaximumScale = mViewportInitialScale; - } else if (mViewportInitialScale == 0) { - mViewportInitialScale = mViewportMaximumScale; - } - } - if (mViewportWidth < 0 && mViewportInitialScale == 100) { - mViewportWidth = 0; - } - - // now notify webview - if (mWebView != null) { - HashMap scaleLimit = new HashMap(); - scaleLimit.put("minScale", mViewportMinimumScale); - scaleLimit.put("maxScale", mViewportMaximumScale); - - if (mRestoredScale > 0) { - Message.obtain(mWebView.mPrivateHandler, - WebView.DID_FIRST_LAYOUT_MSG_ID, mRestoredScale, 0, - scaleLimit).sendToTarget(); - mRestoredScale = 0; - } else { - Message.obtain(mWebView.mPrivateHandler, - WebView.DID_FIRST_LAYOUT_MSG_ID, mViewportInitialScale, - mViewportWidth, scaleLimit).sendToTarget(); - } - - // if no restored offset, move the new page to (0, 0) - Message.obtain(mWebView.mPrivateHandler, WebView.SCROLL_TO_MSG_ID, - mRestoredX, mRestoredY).sendToTarget(); - mRestoredX = mRestoredY = 0; - - // force an early draw for quick feedback after the first layout - if (mCurrentViewWidth != 0) { - synchronized (this) { - if (mDrawIsScheduled) { - mEventHub.removeMessages(EventHub.WEBKIT_DRAW); - } - mDrawIsScheduled = true; - mEventHub.sendMessageAtFrontOfQueue(Message.obtain(null, - EventHub.WEBKIT_DRAW)); - } - } - } - } - - // called by JNI - private void restoreScale(int scale) { - if (mBrowserFrame.firstLayoutDone() == false) { - 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) { - if (mWebView != null) { - Message msg = Message.obtain(mWebView.mPrivateHandler, - WebView.UPDATE_TEXTFIELD_TEXT_MSG_ID, ptr, - textGeneration, text); - msg.getData().putBoolean("password", changeToPassword); - msg.sendToTarget(); - } - } - - // 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, - int[] selectedArray) { - if (mWebView != null) { - mWebView.requestListBox(array, enabledArray, selectedArray); - } - } - - // called by JNI - private void requestListBox(String[] array, boolean[] enabledArray, - int selection) { - if (mWebView != null) { - mWebView.requestListBox(array, enabledArray, selection); - } - - } -} diff --git a/core/java/android/webkit/WebViewDatabase.java b/core/java/android/webkit/WebViewDatabase.java deleted file mode 100644 index 1004e30..0000000 --- a/core/java/android/webkit/WebViewDatabase.java +++ /dev/null @@ -1,967 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.webkit; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Set; -import java.util.Map.Entry; - -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.database.DatabaseUtils; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteStatement; -import android.util.Log; -import android.webkit.CookieManager.Cookie; -import android.webkit.CacheManager.CacheResult; - -public class WebViewDatabase { - private static final String DATABASE_FILE = "webview.db"; - private static final String CACHE_DATABASE_FILE = "webviewCache.db"; - - // log tag - protected static final String LOGTAG = "webviewdatabase"; - - 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 - // 5 -> 4 Removed Oma-Downoads table - // 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; - - private static SQLiteDatabase mDatabase = null; - private static SQLiteDatabase mCacheDatabase = null; - - // synchronize locks - private final Object mCookieLock = new Object(); - private final Object mPasswordLock = new Object(); - private final Object mFormLock = new Object(); - private final Object mHttpAuthLock = new Object(); - - private static final String mTableNames[] = { - "cookies", "password", "formurl", "formdata", "httpauth" - }; - - // Table ids (they are index to mTableNames) - private static final int TABLE_COOKIES_ID = 0; - - private static final int TABLE_PASSWORD_ID = 1; - - private static final int TABLE_FORMURL_ID = 2; - - private static final int TABLE_FORMDATA_ID = 3; - - private static final int TABLE_HTTPAUTH_ID = 4; - - // column id strings for "_id" which can be used by any table - private static final String ID_COL = "_id"; - - private static final String[] ID_PROJECTION = new String[] { - "_id" - }; - - // column id strings for "cookies" table - private static final String COOKIES_NAME_COL = "name"; - - private static final String COOKIES_VALUE_COL = "value"; - - private static final String COOKIES_DOMAIN_COL = "domain"; - - private static final String COOKIES_PATH_COL = "path"; - - private static final String COOKIES_EXPIRES_COL = "expires"; - - private static final String COOKIES_SECURE_COL = "secure"; - - // column id strings for "cache" table - private static final String CACHE_URL_COL = "url"; - - private static final String CACHE_FILE_PATH_COL = "filepath"; - - private static final String CACHE_LAST_MODIFY_COL = "lastmodify"; - - private static final String CACHE_ETAG_COL = "etag"; - - private static final String CACHE_EXPIRES_COL = "expires"; - - private static final String CACHE_MIMETYPE_COL = "mimetype"; - - private static final String CACHE_ENCODING_COL = "encoding"; - - private static final String CACHE_HTTP_STATUS_COL = "httpstatus"; - - private static final String CACHE_LOCATION_COL = "location"; - - private static final String CACHE_CONTENTLENGTH_COL = "contentlength"; - - // column id strings for "password" table - private static final String PASSWORD_HOST_COL = "host"; - - private static final String PASSWORD_USERNAME_COL = "username"; - - private static final String PASSWORD_PASSWORD_COL = "password"; - - // column id strings for "formurl" table - private static final String FORMURL_URL_COL = "url"; - - // column id strings for "formdata" table - private static final String FORMDATA_URLID_COL = "urlid"; - - private static final String FORMDATA_NAME_COL = "name"; - - private static final String FORMDATA_VALUE_COL = "value"; - - // column id strings for "httpauth" table - private static final String HTTPAUTH_HOST_COL = "host"; - - private static final String HTTPAUTH_REALM_COL = "realm"; - - private static final String HTTPAUTH_USERNAME_COL = "username"; - - private static final String HTTPAUTH_PASSWORD_COL = "password"; - - // use InsertHelper to improve insert performance by 40% - private static DatabaseUtils.InsertHelper mCacheInserter; - private static int mCacheUrlColIndex; - private static int mCacheFilePathColIndex; - private static int mCacheLastModifyColIndex; - private static int mCacheETagColIndex; - private static int mCacheExpiresColIndex; - private static int mCacheMimeTypeColIndex; - private static int mCacheEncodingColIndex; - private static int mCacheHttpStatusColIndex; - private static int mCacheLocationColIndex; - private static int mCacheContentLengthColIndex; - - private static int mCacheTransactionRefcount; - - private WebViewDatabase() { - // Singleton only, use getInstance() - } - - public static synchronized WebViewDatabase getInstance(Context context) { - if (mInstance == null) { - mInstance = new WebViewDatabase(); - mDatabase = context.openOrCreateDatabase(DATABASE_FILE, 0, null); - - // mDatabase should not be null, - // the only case is RequestAPI test has problem to create db - if (mDatabase != null && mDatabase.getVersion() != DATABASE_VERSION) { - mDatabase.beginTransaction(); - try { - upgradeDatabase(); - mDatabase.setTransactionSuccessful(); - } finally { - mDatabase.endTransaction(); - } - } - - if (mDatabase != null) { - // use per table Mutex lock, turn off database lock, this - // improves performance as database's ReentrantLock is expansive - mDatabase.setLockingEnabled(false); - } - - mCacheDatabase = context.openOrCreateDatabase(CACHE_DATABASE_FILE, - 0, null); - - // mCacheDatabase should not be null, - // the only case is RequestAPI test has problem to create db - if (mCacheDatabase != null - && mCacheDatabase.getVersion() != CACHE_DATABASE_VERSION) { - mCacheDatabase.beginTransaction(); - try { - upgradeCacheDatabase(); - bootstrapCacheDatabase(); - mCacheDatabase.setTransactionSuccessful(); - } 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) { - // use InsertHelper for faster insertion - mCacheInserter = new DatabaseUtils.InsertHelper(mCacheDatabase, - "cache"); - mCacheUrlColIndex = mCacheInserter - .getColumnIndex(CACHE_URL_COL); - mCacheFilePathColIndex = mCacheInserter - .getColumnIndex(CACHE_FILE_PATH_COL); - mCacheLastModifyColIndex = mCacheInserter - .getColumnIndex(CACHE_LAST_MODIFY_COL); - mCacheETagColIndex = mCacheInserter - .getColumnIndex(CACHE_ETAG_COL); - mCacheExpiresColIndex = mCacheInserter - .getColumnIndex(CACHE_EXPIRES_COL); - mCacheMimeTypeColIndex = mCacheInserter - .getColumnIndex(CACHE_MIMETYPE_COL); - mCacheEncodingColIndex = mCacheInserter - .getColumnIndex(CACHE_ENCODING_COL); - mCacheHttpStatusColIndex = mCacheInserter - .getColumnIndex(CACHE_HTTP_STATUS_COL); - mCacheLocationColIndex = mCacheInserter - .getColumnIndex(CACHE_LOCATION_COL); - mCacheContentLengthColIndex = mCacheInserter - .getColumnIndex(CACHE_CONTENTLENGTH_COL); - } - } - - return mInstance; - } - - private static void upgradeDatabase() { - int oldVersion = mDatabase.getVersion(); - if (oldVersion != 0) { - Log.i(LOGTAG, "Upgrading database from version " - + oldVersion + " to " - + 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_PASSWORD_ID]); - - mDatabase.setVersion(DATABASE_VERSION); - - if (!justPasswords) { - // cookies - mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_COOKIES_ID] - + " (" + ID_COL + " INTEGER PRIMARY KEY, " - + COOKIES_NAME_COL + " TEXT, " + COOKIES_VALUE_COL - + " TEXT, " + COOKIES_DOMAIN_COL + " TEXT, " - + COOKIES_PATH_COL + " TEXT, " + COOKIES_EXPIRES_COL - + " INTEGER, " + COOKIES_SECURE_COL + " INTEGER" + ");"); - mDatabase.execSQL("CREATE INDEX cookiesIndex ON " - + mTableNames[TABLE_COOKIES_ID] + " (path)"); - - // formurl - mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_FORMURL_ID] - + " (" + ID_COL + " INTEGER PRIMARY KEY, " + FORMURL_URL_COL - + " TEXT" + ");"); - - // formdata - mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_FORMDATA_ID] - + " (" + ID_COL + " INTEGER PRIMARY KEY, " - + FORMDATA_URLID_COL + " INTEGER, " + FORMDATA_NAME_COL - + " TEXT, " + FORMDATA_VALUE_COL + " TEXT," + " UNIQUE (" - + FORMDATA_URLID_COL + ", " + FORMDATA_NAME_COL + ", " - + FORMDATA_VALUE_COL + ") ON CONFLICT IGNORE);"); - - // httpauth - mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_HTTPAUTH_ID] - + " (" + ID_COL + " INTEGER PRIMARY KEY, " - + HTTPAUTH_HOST_COL + " TEXT, " + HTTPAUTH_REALM_COL - + " TEXT, " + HTTPAUTH_USERNAME_COL + " TEXT, " - + HTTPAUTH_PASSWORD_COL + " TEXT," + " UNIQUE (" - + 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() { - int oldVersion = mCacheDatabase.getVersion(); - if (oldVersion != 0) { - Log.i(LOGTAG, "Upgrading cache database from version " - + oldVersion + " to " - + DATABASE_VERSION + ", which will destroy all old data"); - } - mCacheDatabase.execSQL("DROP TABLE IF EXISTS cache"); - mCacheDatabase.setVersion(CACHE_DATABASE_VERSION); - } - - private static void bootstrapCacheDatabase() { - if (mCacheDatabase != null) { - mCacheDatabase.execSQL("CREATE TABLE cache" - + " (" + ID_COL + " INTEGER PRIMARY KEY, " + CACHE_URL_COL - + " TEXT, " + CACHE_FILE_PATH_COL + " TEXT, " - + CACHE_LAST_MODIFY_COL + " TEXT, " + CACHE_ETAG_COL - + " TEXT, " + CACHE_EXPIRES_COL + " INTEGER, " - + CACHE_MIMETYPE_COL + " TEXT, " + CACHE_ENCODING_COL - + " TEXT," + CACHE_HTTP_STATUS_COL + " INTEGER, " - + CACHE_LOCATION_COL + " TEXT, " + CACHE_CONTENTLENGTH_COL - + " INTEGER, " + " UNIQUE (" + CACHE_URL_COL - + ") ON CONFLICT REPLACE);"); - mCacheDatabase.execSQL("CREATE INDEX cacheUrlIndex ON cache (" - + CACHE_URL_COL + ")"); - } - } - - private boolean hasEntries(int tableId) { - if (mDatabase == null) { - return false; - } - - Cursor cursor = mDatabase.query(mTableNames[tableId], ID_PROJECTION, - null, null, null, null, null); - boolean ret = cursor.moveToFirst() == true; - cursor.close(); - return ret; - } - - // - // cookies functions - // - - /** - * Get cookies in the format of CookieManager.Cookie inside an ArrayList for - * a given domain - * - * @return ArrayList<Cookie> If nothing is found, return an empty list. - */ - ArrayList<Cookie> getCookiesForDomain(String domain) { - ArrayList<Cookie> list = new ArrayList<Cookie>(); - if (domain == null || mDatabase == null) { - return list; - } - - synchronized (mCookieLock) { - final String[] columns = new String[] { - ID_COL, COOKIES_DOMAIN_COL, COOKIES_PATH_COL, - COOKIES_NAME_COL, COOKIES_VALUE_COL, COOKIES_EXPIRES_COL, - COOKIES_SECURE_COL - }; - final String selection = "(" + COOKIES_DOMAIN_COL - + " GLOB '*' || ?)"; - Cursor cursor = mDatabase.query(mTableNames[TABLE_COOKIES_ID], - columns, selection, new String[] { domain }, null, null, - null); - if (cursor.moveToFirst()) { - int domainCol = cursor.getColumnIndex(COOKIES_DOMAIN_COL); - int pathCol = cursor.getColumnIndex(COOKIES_PATH_COL); - int nameCol = cursor.getColumnIndex(COOKIES_NAME_COL); - int valueCol = cursor.getColumnIndex(COOKIES_VALUE_COL); - int expiresCol = cursor.getColumnIndex(COOKIES_EXPIRES_COL); - int secureCol = cursor.getColumnIndex(COOKIES_SECURE_COL); - do { - Cookie cookie = new Cookie(); - cookie.domain = cursor.getString(domainCol); - cookie.path = cursor.getString(pathCol); - cookie.name = cursor.getString(nameCol); - cookie.value = cursor.getString(valueCol); - if (cursor.isNull(expiresCol)) { - cookie.expires = -1; - } else { - cookie.expires = cursor.getLong(expiresCol); - } - cookie.secure = cursor.getShort(secureCol) != 0; - cookie.mode = Cookie.MODE_NORMAL; - list.add(cookie); - } while (cursor.moveToNext()); - } - cursor.close(); - return list; - } - } - - /** - * Delete cookies which matches (domain, path, name). - * - * @param domain If it is null, nothing happens. - * @param path If it is null, all the cookies match (domain) will be - * deleted. - * @param name If it is null, all the cookies match (domain, path) will be - * deleted. - */ - void deleteCookies(String domain, String path, String name) { - if (domain == null || mDatabase == null) { - return; - } - - synchronized (mCookieLock) { - final String where = "(" + COOKIES_DOMAIN_COL + " == ?) AND (" - + COOKIES_PATH_COL + " == ?) AND (" + COOKIES_NAME_COL - + " == ?)"; - mDatabase.delete(mTableNames[TABLE_COOKIES_ID], where, - new String[] { domain, path, name }); - } - } - - /** - * Add a cookie to the database - * - * @param cookie - */ - void addCookie(Cookie cookie) { - if (cookie.domain == null || cookie.path == null || cookie.name == null - || mDatabase == null) { - return; - } - - synchronized (mCookieLock) { - ContentValues cookieVal = new ContentValues(); - cookieVal.put(COOKIES_DOMAIN_COL, cookie.domain); - cookieVal.put(COOKIES_PATH_COL, cookie.path); - cookieVal.put(COOKIES_NAME_COL, cookie.name); - cookieVal.put(COOKIES_VALUE_COL, cookie.value); - if (cookie.expires != -1) { - cookieVal.put(COOKIES_EXPIRES_COL, cookie.expires); - } - cookieVal.put(COOKIES_SECURE_COL, cookie.secure); - mDatabase.insert(mTableNames[TABLE_COOKIES_ID], null, cookieVal); - } - } - - /** - * Whether there is any cookies in the database - * - * @return TRUE if there is cookie. - */ - boolean hasCookies() { - synchronized (mCookieLock) { - return hasEntries(TABLE_COOKIES_ID); - } - } - - /** - * Clear cookie database - */ - void clearCookies() { - if (mDatabase == null) { - return; - } - - synchronized (mCookieLock) { - mDatabase.delete(mTableNames[TABLE_COOKIES_ID], null, null); - } - } - - /** - * Clear session cookies, which means cookie doesn't have EXPIRES. - */ - void clearSessionCookies() { - if (mDatabase == null) { - return; - } - - final String sessionExpired = COOKIES_EXPIRES_COL + " ISNULL"; - synchronized (mCookieLock) { - mDatabase.delete(mTableNames[TABLE_COOKIES_ID], sessionExpired, - null); - } - } - - /** - * Clear expired cookies - * - * @param now Time for now - */ - void clearExpiredCookies(long now) { - if (mDatabase == null) { - return; - } - - final String expires = COOKIES_EXPIRES_COL + " <= ?"; - synchronized (mCookieLock) { - mDatabase.delete(mTableNames[TABLE_COOKIES_ID], expires, - new String[] { Long.toString(now) }); - } - } - - // - // cache functions, can only be called from WebCoreThread - // - - boolean startCacheTransaction() { - if (++mCacheTransactionRefcount == 1) { - mCacheDatabase.beginTransaction(); - return true; - } - return false; - } - - boolean endCacheTransaction() { - if (--mCacheTransactionRefcount == 0) { - try { - mCacheDatabase.setTransactionSuccessful(); - } finally { - mCacheDatabase.endTransaction(); - } - return true; - } - return false; - } - - /** - * Get a cache item. - * - * @param url The url - * @return CacheResult The CacheManager.CacheResult - */ - CacheResult getCache(String url) { - if (url == null || mCacheDatabase == null) { - return null; - } - - Cursor cursor = mCacheDatabase.rawQuery("SELECT filepath, lastmodify, etag, expires, " - + "mimetype, encoding, httpstatus, location, contentlength " - + "FROM cache WHERE url = ?", - new String[] { url }); - - try { - if (cursor.moveToFirst()) { - CacheResult ret = new CacheResult(); - ret.localPath = cursor.getString(0); - ret.lastModified = cursor.getString(1); - ret.etag = cursor.getString(2); - ret.expires = cursor.getLong(3); - ret.mimeType = cursor.getString(4); - ret.encoding = cursor.getString(5); - ret.httpStatusCode = cursor.getInt(6); - ret.location = cursor.getString(7); - ret.contentLength = cursor.getLong(8); - return ret; - } - } finally { - if (cursor != null) cursor.close(); - } - return null; - } - - /** - * Remove a cache item. - * - * @param url The url - */ - void removeCache(String url) { - if (url == null || mCacheDatabase == null) { - return; - } - - mCacheDatabase.execSQL("DELETE FROM cache WHERE url = ?", new String[] { url }); - } - - /** - * Add or update a cache. CACHE_URL_COL is unique in the table. - * - * @param url The url - * @param c The CacheManager.CacheResult - */ - void addCache(String url, CacheResult c) { - if (url == null || mCacheDatabase == null) { - return; - } - - mCacheInserter.prepareForInsert(); - mCacheInserter.bind(mCacheUrlColIndex, url); - mCacheInserter.bind(mCacheFilePathColIndex, c.localPath); - mCacheInserter.bind(mCacheLastModifyColIndex, c.lastModified); - mCacheInserter.bind(mCacheETagColIndex, c.etag); - mCacheInserter.bind(mCacheExpiresColIndex, c.expires); - mCacheInserter.bind(mCacheMimeTypeColIndex, c.mimeType); - mCacheInserter.bind(mCacheEncodingColIndex, c.encoding); - mCacheInserter.bind(mCacheHttpStatusColIndex, c.httpStatusCode); - mCacheInserter.bind(mCacheLocationColIndex, c.location); - mCacheInserter.bind(mCacheContentLengthColIndex, c.contentLength); - mCacheInserter.execute(); - } - - /** - * Clear cache database - */ - void clearCache() { - if (mCacheDatabase == null) { - return; - } - - mCacheDatabase.delete("cache", null, null); - } - - boolean hasCache() { - if (mCacheDatabase == null) { - return false; - } - - Cursor cursor = mCacheDatabase.query("cache", ID_PROJECTION, - null, null, null, null, null); - boolean ret = cursor.moveToFirst() == true; - cursor.close(); - return ret; - } - - long getCacheTotalSize() { - long size = 0; - Cursor cursor = mCacheDatabase.rawQuery( - "SELECT SUM(contentlength) as sum FROM cache", null); - if (cursor.moveToFirst()) { - size = cursor.getLong(0); - } - cursor.close(); - return size; - } - - ArrayList<String> trimCache(long amount) { - ArrayList<String> pathList = new ArrayList<String>(100); - Cursor cursor = mCacheDatabase.rawQuery( - "SELECT contentlength, filepath FROM cache ORDER BY expires ASC", - null); - if (cursor.moveToFirst()) { - int batchSize = 100; - StringBuilder pathStr = new StringBuilder(20 + 16 * batchSize); - pathStr.append("DELETE FROM cache WHERE filepath IN (?"); - for (int i = 1; i < batchSize; i++) { - pathStr.append(", ?"); - } - pathStr.append(")"); - SQLiteStatement statement = mCacheDatabase.compileStatement(pathStr - .toString()); - // as bindString() uses 1-based index, initialize index to 1 - int index = 1; - do { - long length = cursor.getLong(0); - if (length == 0) { - continue; - } - amount -= length; - String filePath = cursor.getString(1); - statement.bindString(index, filePath); - pathList.add(filePath); - if (index++ == batchSize) { - statement.execute(); - statement.clearBindings(); - index = 1; - } - } while (cursor.moveToNext() && amount > 0); - if (index > 1) { - // there may be old bindings from the previous statement if - // index is less than batchSize, which is Ok. - statement.execute(); - } - statement.close(); - } - cursor.close(); - return pathList; - } - - // - // password functions - // - - /** - * Set password. Tuple (PASSWORD_HOST_COL, PASSWORD_USERNAME_COL) is unique. - * - * @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 schemePlusHost, String username, - String password) { - if (schemePlusHost == null || mDatabase == null) { - return; - } - - synchronized (mPasswordLock) { - final ContentValues c = new ContentValues(); - 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, - c); - } - } - - /** - * Retrieve the username and password for a given host - * - * @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 schemePlusHost) { - if (schemePlusHost == null || mDatabase == null) { - return null; - } - - final String[] columns = new String[] { - PASSWORD_USERNAME_COL, PASSWORD_PASSWORD_COL - }; - final String selection = "(" + PASSWORD_HOST_COL + " == ?)"; - synchronized (mPasswordLock) { - String[] ret = null; - Cursor cursor = mDatabase.query(mTableNames[TABLE_PASSWORD_ID], - columns, selection, new String[] { schemePlusHost }, null, - null, null); - if (cursor.moveToFirst()) { - ret = new String[2]; - ret[0] = cursor.getString( - cursor.getColumnIndex(PASSWORD_USERNAME_COL)); - ret[1] = cursor.getString( - cursor.getColumnIndex(PASSWORD_PASSWORD_COL)); - } - cursor.close(); - return ret; - } - } - - /** - * Find out if there are any passwords saved. - * - * @return TRUE if there is passwords saved - */ - public boolean hasUsernamePassword() { - synchronized (mPasswordLock) { - return hasEntries(TABLE_PASSWORD_ID); - } - } - - /** - * Clear password database - */ - public void clearUsernamePassword() { - if (mDatabase == null) { - return; - } - - synchronized (mPasswordLock) { - mDatabase.delete(mTableNames[TABLE_PASSWORD_ID], null, null); - } - } - - // - // http authentication password functions - // - - /** - * Set HTTP authentication password. Tuple (HTTPAUTH_HOST_COL, - * HTTPAUTH_REALM_COL, HTTPAUTH_USERNAME_COL) is unique. - * - * @param host The host for the password - * @param realm The realm 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 setHttpAuthUsernamePassword(String host, String realm, String username, - String password) { - if (host == null || realm == null || mDatabase == null) { - return; - } - - synchronized (mHttpAuthLock) { - final ContentValues c = new ContentValues(); - c.put(HTTPAUTH_HOST_COL, host); - c.put(HTTPAUTH_REALM_COL, realm); - c.put(HTTPAUTH_USERNAME_COL, username); - c.put(HTTPAUTH_PASSWORD_COL, password); - mDatabase.insert(mTableNames[TABLE_HTTPAUTH_ID], HTTPAUTH_HOST_COL, - c); - } - } - - /** - * Retrieve the HTTP authentication username and password for a given - * host+realm pair - * - * @param host The host the password applies to - * @param realm The realm the password 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[] getHttpAuthUsernamePassword(String host, String realm) { - if (host == null || realm == null || mDatabase == null){ - return null; - } - - final String[] columns = new String[] { - HTTPAUTH_USERNAME_COL, HTTPAUTH_PASSWORD_COL - }; - final String selection = "(" + HTTPAUTH_HOST_COL + " == ?) AND (" - + HTTPAUTH_REALM_COL + " == ?)"; - synchronized (mHttpAuthLock) { - String[] ret = null; - Cursor cursor = mDatabase.query(mTableNames[TABLE_HTTPAUTH_ID], - columns, selection, new String[] { host, realm }, null, - null, null); - if (cursor.moveToFirst()) { - ret = new String[2]; - ret[0] = cursor.getString( - cursor.getColumnIndex(HTTPAUTH_USERNAME_COL)); - ret[1] = cursor.getString( - cursor.getColumnIndex(HTTPAUTH_PASSWORD_COL)); - } - cursor.close(); - return ret; - } - } - - /** - * Find out if there are any HTTP authentication passwords saved. - * - * @return TRUE if there are passwords saved - */ - public boolean hasHttpAuthUsernamePassword() { - synchronized (mHttpAuthLock) { - return hasEntries(TABLE_HTTPAUTH_ID); - } - } - - /** - * Clear HTTP authentication password database - */ - public void clearHttpAuthUsernamePassword() { - if (mDatabase == null) { - return; - } - - synchronized (mHttpAuthLock) { - mDatabase.delete(mTableNames[TABLE_HTTPAUTH_ID], null, null); - } - } - - // - // form data functions - // - - /** - * Set form data for a site. Tuple (FORMDATA_URLID_COL, FORMDATA_NAME_COL, - * FORMDATA_VALUE_COL) is unique - * - * @param url The url of the site - * @param formdata The form data in HashMap - */ - void setFormData(String url, HashMap<String, String> formdata) { - if (url == null || formdata == null || mDatabase == null) { - return; - } - - final String selection = "(" + FORMURL_URL_COL + " == ?)"; - synchronized (mFormLock) { - long urlid = -1; - Cursor cursor = mDatabase.query(mTableNames[TABLE_FORMURL_ID], - ID_PROJECTION, selection, new String[] { url }, null, null, - null); - if (cursor.moveToFirst()) { - urlid = cursor.getLong(cursor.getColumnIndex(ID_COL)); - } else { - ContentValues c = new ContentValues(); - c.put(FORMURL_URL_COL, url); - urlid = mDatabase.insert( - mTableNames[TABLE_FORMURL_ID], null, c); - } - cursor.close(); - if (urlid >= 0) { - Set<Entry<String, String>> set = formdata.entrySet(); - Iterator<Entry<String, String>> iter = set.iterator(); - ContentValues map = new ContentValues(); - map.put(FORMDATA_URLID_COL, urlid); - while (iter.hasNext()) { - Entry<String, String> entry = iter.next(); - map.put(FORMDATA_NAME_COL, entry.getKey()); - map.put(FORMDATA_VALUE_COL, entry.getValue()); - mDatabase.insert(mTableNames[TABLE_FORMDATA_ID], null, map); - } - } - } - } - - /** - * Get all the values for a form entry with "name" in a given site - * - * @param url The url of the site - * @param name The name of the form entry - * @return A list of values. Return empty list if nothing is found. - */ - ArrayList<String> getFormData(String url, String name) { - ArrayList<String> values = new ArrayList<String>(); - if (url == null || name == null || mDatabase == null) { - return values; - } - - final String urlSelection = "(" + FORMURL_URL_COL + " == ?)"; - final String dataSelection = "(" + FORMDATA_URLID_COL + " == ?) AND (" - + FORMDATA_NAME_COL + " == ?)"; - synchronized (mFormLock) { - Cursor cursor = mDatabase.query(mTableNames[TABLE_FORMURL_ID], - ID_PROJECTION, urlSelection, new String[] { url }, null, - null, null); - if (cursor.moveToFirst()) { - long urlid = cursor.getLong(cursor.getColumnIndex(ID_COL)); - Cursor dataCursor = mDatabase.query( - mTableNames[TABLE_FORMDATA_ID], - new String[] { ID_COL, FORMDATA_VALUE_COL }, - dataSelection, - new String[] { Long.toString(urlid), name }, null, - null, null); - if (dataCursor.moveToFirst()) { - int valueCol = - dataCursor.getColumnIndex(FORMDATA_VALUE_COL); - do { - values.add(dataCursor.getString(valueCol)); - } while (dataCursor.moveToNext()); - } - dataCursor.close(); - } - cursor.close(); - return values; - } - } - - /** - * Find out if there is form data saved. - * - * @return TRUE if there is form data in the database - */ - public boolean hasFormData() { - synchronized (mFormLock) { - return hasEntries(TABLE_FORMURL_ID); - } - } - - /** - * Clear form database - */ - public void clearFormData() { - if (mDatabase == null) { - return; - } - - synchronized (mFormLock) { - mDatabase.delete(mTableNames[TABLE_FORMURL_ID], null, null); - mDatabase.delete(mTableNames[TABLE_FORMDATA_ID], null, null); - } - } -} diff --git a/core/java/android/webkit/gears/AndroidGpsLocationProvider.java b/core/java/android/webkit/gears/AndroidGpsLocationProvider.java deleted file mode 100644 index 3646042..0000000 --- a/core/java/android/webkit/gears/AndroidGpsLocationProvider.java +++ /dev/null @@ -1,156 +0,0 @@ -// 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.location.Location; -import android.location.LocationListener; -import android.location.LocationManager; -import android.location.LocationProvider; -import android.os.Bundle; -import android.util.Log; -import android.webkit.WebView; - -/** - * GPS provider implementation for Android. - */ -public final class AndroidGpsLocationProvider implements LocationListener { - /** - * Logging tag - */ - private static final String TAG = "Gears-J-GpsProvider"; - /** - * Our location manager instance. - */ - private LocationManager locationManager; - /** - * The native object ID. - */ - private long nativeObject; - - public AndroidGpsLocationProvider(WebView webview, long object) { - nativeObject = object; - locationManager = (LocationManager) webview.getContext().getSystemService( - Context.LOCATION_SERVICE); - if (locationManager == null) { - Log.e(TAG, - "AndroidGpsLocationProvider: could not get location manager."); - throw new NullPointerException( - "AndroidGpsLocationProvider: locationManager is null."); - } - // Register for location updates. - try { - locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, - this); - } catch (IllegalArgumentException ex) { - Log.e(TAG, - "AndroidLocationGpsProvider: could not register for updates: " + ex); - throw ex; - } catch (SecurityException ex) { - Log.e(TAG, - "AndroidGpsLocationProvider: not allowed to register for update: " - + ex); - throw ex; - } - } - - /** - * Called when the provider is no longer needed. - */ - public void shutdown() { - locationManager.removeUpdates(this); - Log.i(TAG, "GPS provider closed."); - } - - /** - * Called when the location has changed. - * @param location The new location, as a Location object. - */ - public void onLocationChanged(Location location) { - Log.i(TAG, "Location changed: " + location); - nativeLocationChanged(location, nativeObject); - } - - /** - * Called when the provider status changes. - * - * @param provider the name of the location provider associated with this - * update. - * @param status {@link LocationProvider#OUT_OF_SERVICE} if the - * provider is out of service, and this is not expected to change in the - * near future; {@link LocationProvider#TEMPORARILY_UNAVAILABLE} if - * the provider is temporarily unavailable but is expected to be available - * shortly; and {@link LocationProvider#AVAILABLE} if the - * provider is currently available. - * @param extras an optional Bundle which will contain provider specific - * status variables (such as number of satellites). - */ - public void onStatusChanged(String provider, int status, Bundle extras) { - Log.i(TAG, "Provider " + provider + " status changed to " + status); - if (status == LocationProvider.OUT_OF_SERVICE || - status == LocationProvider.TEMPORARILY_UNAVAILABLE) { - nativeProviderError(false, nativeObject); - } - } - - /** - * Called when the provider is enabled. - * - * @param provider the name of the location provider that is now enabled. - */ - public void onProviderEnabled(String provider) { - Log.i(TAG, "Provider " + provider + " enabled."); - // No need to notify the native side. It's enough to start sending - // valid position fixes again. - } - - /** - * Called when the provider is disabled. - * - * @param provider the name of the location provider that is now disabled. - */ - public void onProviderDisabled(String provider) { - Log.i(TAG, "Provider " + provider + " disabled."); - nativeProviderError(true, nativeObject); - } - - /** - * The native method called when a new location is available. - * @param location is the new Location instance to pass to the native side. - * @param nativeObject is a pointer to the corresponding - * AndroidGpsLocationProvider C++ instance. - */ - private native void nativeLocationChanged(Location location, long object); - - /** - * The native method called when there is a GPS provder error. - * @param isDisabled is true when the error signifies the fact that the GPS - * HW is disabled. For other errors, this param is always false. - * @param nativeObject is a pointer to the corresponding - * AndroidGpsLocationProvider C++ instance. - */ - private native void nativeProviderError(boolean isDisabled, long object); -} diff --git a/core/java/android/webkit/gears/AndroidRadioDataProvider.java b/core/java/android/webkit/gears/AndroidRadioDataProvider.java deleted file mode 100644 index c920d45..0000000 --- a/core/java/android/webkit/gears/AndroidRadioDataProvider.java +++ /dev/null @@ -1,244 +0,0 @@ -// 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.telephony.CellLocation; -import android.telephony.ServiceState; -import android.telephony.gsm.GsmCellLocation; -import android.telephony.PhoneStateListener; -import android.telephony.TelephonyManager; -import android.util.Log; -import android.webkit.WebView; - -/** - * Radio data provider implementation for Android. - */ -public final class AndroidRadioDataProvider extends PhoneStateListener { - - /** Logging tag */ - private static final String TAG = "Gears-J-RadioProvider"; - - /** Network types */ - private static final int RADIO_TYPE_UNKNOWN = 0; - private static final int RADIO_TYPE_GSM = 1; - private static final int RADIO_TYPE_WCDMA = 2; - - /** Simple container for radio data */ - public static final class RadioData { - public int cellId = -1; - public int locationAreaCode = -1; - public int signalStrength = -1; - public int mobileCountryCode = -1; - public int mobileNetworkCode = -1; - public int homeMobileCountryCode = -1; - public int homeMobileNetworkCode = -1; - public int radioType = RADIO_TYPE_UNKNOWN; - public String carrierName; - - /** - * Constructs radioData object from the given telephony data. - * @param telephonyManager contains the TelephonyManager instance. - * @param cellLocation contains information about the current GSM cell. - * @param signalStrength is the strength of the network signal. - * @param serviceState contains information about the network service. - * @return a new RadioData object populated with the currently - * available network information or null if there isn't - * enough information. - */ - public static RadioData getInstance(TelephonyManager telephonyManager, - CellLocation cellLocation, int signalStrength, - ServiceState serviceState) { - - if (!(cellLocation instanceof GsmCellLocation)) { - // This also covers the case when cellLocation is null. - // When that happens, we do not bother creating a - // RadioData instance. - return null; - } - - RadioData radioData = new RadioData(); - GsmCellLocation gsmCellLocation = (GsmCellLocation) cellLocation; - - // Extract the cell id, LAC, and signal strength. - radioData.cellId = gsmCellLocation.getCid(); - radioData.locationAreaCode = gsmCellLocation.getLac(); - radioData.signalStrength = signalStrength; - - // Extract the home MCC and home MNC. - String operator = telephonyManager.getSimOperator(); - radioData.setMobileCodes(operator, true); - - if (serviceState != null) { - // Extract the carrier name. - radioData.carrierName = serviceState.getOperatorAlphaLong(); - - // Extract the MCC and MNC. - operator = serviceState.getOperatorNumeric(); - radioData.setMobileCodes(operator, false); - } - - // Finally get the radio type. - int type = telephonyManager.getNetworkType(); - if (type == TelephonyManager.NETWORK_TYPE_UMTS) { - radioData.radioType = RADIO_TYPE_WCDMA; - } else if (type == TelephonyManager.NETWORK_TYPE_GPRS - || type == TelephonyManager.NETWORK_TYPE_EDGE) { - radioData.radioType = RADIO_TYPE_GSM; - } - - // Print out what we got. - Log.i(TAG, "Got the following data:"); - Log.i(TAG, "CellId: " + radioData.cellId); - Log.i(TAG, "LAC: " + radioData.locationAreaCode); - Log.i(TAG, "MNC: " + radioData.mobileNetworkCode); - Log.i(TAG, "MCC: " + radioData.mobileCountryCode); - Log.i(TAG, "home MNC: " + radioData.homeMobileNetworkCode); - Log.i(TAG, "home MCC: " + radioData.homeMobileCountryCode); - Log.i(TAG, "Signal strength: " + radioData.signalStrength); - Log.i(TAG, "Carrier: " + radioData.carrierName); - Log.i(TAG, "Network type: " + radioData.radioType); - - return radioData; - } - - private RadioData() {} - - /** - * Parses a string containing a mobile country code and a mobile - * network code and sets the corresponding member variables. - * @param codes is the string to parse. - * @param homeValues flags whether the codes are for the home operator. - */ - private void setMobileCodes(String codes, boolean homeValues) { - if (codes != null) { - try { - // The operator numeric format is 3 digit country code plus 2 or - // 3 digit network code. - int mcc = Integer.parseInt(codes.substring(0, 3)); - int mnc = Integer.parseInt(codes.substring(3)); - if (homeValues) { - homeMobileCountryCode = mcc; - homeMobileNetworkCode = mnc; - } else { - mobileCountryCode = mcc; - mobileNetworkCode = mnc; - } - } catch (IndexOutOfBoundsException ex) { - Log.e( - TAG, - "AndroidRadioDataProvider: Invalid operator numeric data: " + ex); - } catch (NumberFormatException ex) { - Log.e( - TAG, - "AndroidRadioDataProvider: Operator numeric format error: " + ex); - } - } - } - }; - - /** The native object ID */ - private long nativeObject; - - /** The last known cellLocation */ - private CellLocation cellLocation = null; - - /** The last known signal strength */ - private int signalStrength = -1; - - /** The last known serviceState */ - private ServiceState serviceState = null; - - /** - * Our TelephonyManager instance. - */ - private TelephonyManager telephonyManager; - - /** - * Public constructor. Uses the webview to get the Context object. - */ - public AndroidRadioDataProvider(WebView webview, long object) { - super(); - nativeObject = object; - telephonyManager = (TelephonyManager) webview.getContext().getSystemService( - Context.TELEPHONY_SERVICE); - if (telephonyManager == null) { - Log.e(TAG, - "AndroidRadioDataProvider: could not get tepephony manager."); - throw new NullPointerException( - "AndroidRadioDataProvider: telephonyManager is null."); - } - - // Register for cell id, signal strength and service state changed - // notifications. - telephonyManager.listen(this, PhoneStateListener.LISTEN_CELL_LOCATION - | PhoneStateListener.LISTEN_SIGNAL_STRENGTH - | PhoneStateListener.LISTEN_SERVICE_STATE); - } - - /** - * Should be called when the provider is no longer needed. - */ - public void shutdown() { - telephonyManager.listen(this, PhoneStateListener.LISTEN_NONE); - Log.i(TAG, "AndroidRadioDataProvider shutdown."); - } - - @Override - public void onServiceStateChanged(ServiceState state) { - serviceState = state; - notifyListeners(); - } - - @Override - public void onSignalStrengthChanged(int asu) { - signalStrength = asu; - notifyListeners(); - } - - @Override - public void onCellLocationChanged(CellLocation location) { - cellLocation = location; - notifyListeners(); - } - - private void notifyListeners() { - RadioData radioData = RadioData.getInstance(telephonyManager, cellLocation, - signalStrength, serviceState); - if (radioData != null) { - onUpdateAvailable(radioData, nativeObject); - } - } - - /** - * The native method called when new radio data is available. - * @param radioData is the RadioData instance to pass to the native side. - * @param nativeObject is a pointer to the corresponding - * AndroidRadioDataProvider C++ instance. - */ - private static native void onUpdateAvailable( - RadioData radioData, long nativeObject); -} diff --git a/core/java/android/webkit/gears/AndroidWifiDataProvider.java b/core/java/android/webkit/gears/AndroidWifiDataProvider.java deleted file mode 100644 index 7379f59..0000000 --- a/core/java/android/webkit/gears/AndroidWifiDataProvider.java +++ /dev/null @@ -1,136 +0,0 @@ -// 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/ApacheHttpRequestAndroid.java b/core/java/android/webkit/gears/ApacheHttpRequestAndroid.java deleted file mode 100644 index 0569255..0000000 --- a/core/java/android/webkit/gears/ApacheHttpRequestAndroid.java +++ /dev/null @@ -1,1122 +0,0 @@ -// 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.net.http.Headers; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.util.Config; -import android.util.Log; -import android.webkit.CacheManager; -import android.webkit.CacheManager.CacheResult; -import android.webkit.CookieManager; - -import java.io.InputStream; -import java.io.OutputStream; -import java.io.IOException; -import java.lang.StringBuilder; -import java.util.Date; -import java.util.Map; -import java.util.HashMap; -import java.util.Iterator; - -import org.apache.http.Header; -import org.apache.http.HttpEntity; -import org.apache.http.client.params.HttpClientParams; -import org.apache.http.params.HttpParams; -import org.apache.http.params.HttpConnectionParams; -import org.apache.http.params.HttpProtocolParams; -import org.apache.http.HttpResponse; -import org.apache.http.entity.AbstractHttpEntity; -import org.apache.http.client.*; -import org.apache.http.client.methods.*; -import org.apache.http.impl.client.AbstractHttpClient; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.impl.client.DefaultHttpRequestRetryHandler; -import org.apache.http.conn.ssl.StrictHostnameVerifier; -import org.apache.http.impl.cookie.DateUtils; -import org.apache.http.util.CharArrayBuffer; - -import java.util.concurrent.locks.Condition; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; - -/** - * Performs the underlying HTTP/HTTPS GET, POST, HEAD, PUT, DELETE requests. - * <p> These are performed synchronously (blocking). The caller should - * ensure that it is in a background thread if asynchronous behavior - * is required. All data is pushed, so there is no need for JNI native - * callbacks. - * <p> This uses Apache's HttpClient framework to perform most - * of the underlying network activity. The Android brower's cache, - * android.webkit.CacheManager, is also used when caching is enabled, - * and updated with new data. The android.webkit.CookieManager is also - * queried and updated as necessary. - * <p> The public interface is designed to be called by native code - * through JNI, and to simplify coding none of the public methods will - * surface a checked exception. Unchecked exceptions may still be - * raised but only if the system is in an ill state, such as out of - * memory. - * <p> TODO: This isn't plumbed into LocalServer yet. Mutually - * dependent on LocalServer - will attach the two together once both - * are submitted. - */ -public final class ApacheHttpRequestAndroid { - /** Debug logging tag. */ - private static final String LOG_TAG = "Gears-J"; - /** HTTP response header line endings are CR-LF style. */ - private static final String HTTP_LINE_ENDING = "\r\n"; - /** Safe MIME type to use whenever it isn't specified. */ - private static final String DEFAULT_MIME_TYPE = "text/plain"; - /** Case-sensitive header keys */ - public static final String KEY_CONTENT_LENGTH = "Content-Length"; - public static final String KEY_EXPIRES = "Expires"; - public static final String KEY_LAST_MODIFIED = "Last-Modified"; - public static final String KEY_ETAG = "ETag"; - public static final String KEY_LOCATION = "Location"; - public static final String KEY_CONTENT_TYPE = "Content-Type"; - /** Number of bytes to send and receive on the HTTP connection in - * one go. */ - private static final int BUFFER_SIZE = 4096; - - /** The first element of the String[] value in a headers map is the - * unmodified (case-sensitive) key. */ - public static final int HEADERS_MAP_INDEX_KEY = 0; - /** The second element of the String[] value in a headers map is the - * associated value. */ - public static final int HEADERS_MAP_INDEX_VALUE = 1; - - /** Request headers, as key -> value map. */ - // TODO: replace this design by a simpler one (the C++ side has to - // be modified too), where we do not store both the original header - // and the lowercase one. - private Map<String, String[]> mRequestHeaders = - new HashMap<String, String[]>(); - /** Response headers, as a lowercase key -> value map. */ - private Map<String, String[]> mResponseHeaders = - new HashMap<String, String[]>(); - /** The URL used for createCacheResult() */ - private String mCacheResultUrl; - /** CacheResult being saved into, if inserting a new cache entry. */ - private CacheResult mCacheResult; - /** Initialized by initChildThread(). Used to target abort(). */ - private Thread mBridgeThread; - - /** Our HttpClient */ - private AbstractHttpClient mClient; - /** The HttpMethod associated with this request */ - private HttpRequestBase mMethod; - /** The complete response line e.g "HTTP/1.0 200 OK" */ - private String mResponseLine; - /** HTTP body stream, setup after connection. */ - private InputStream mBodyInputStream; - - /** HTTP Response Entity */ - private HttpResponse mResponse; - - /** Post Entity, used to stream the request to the server */ - private StreamEntity mPostEntity = null; - /** Content lenght, mandatory when using POST */ - private long mContentLength; - - /** The request executes in a parallel thread */ - private Thread mHttpThread = null; - /** protect mHttpThread, if interrupt() is called concurrently */ - private Lock mHttpThreadLock = new ReentrantLock(); - /** Flag set to true when the request thread is joined */ - private boolean mConnectionFinished = false; - /** Flag set to true by interrupt() and/or connection errors */ - private boolean mConnectionFailed = false; - /** Lock protecting the access to mConnectionFailed */ - private Lock mConnectionFailedLock = new ReentrantLock(); - - /** Lock on the loop in StreamEntity */ - private Lock mStreamingReadyLock = new ReentrantLock(); - /** Condition variable used to signal the loop is ready... */ - private Condition mStreamingReady = mStreamingReadyLock.newCondition(); - - /** Used to pass around the block of data POSTed */ - private Buffer mBuffer = new Buffer(); - /** Used to signal that the block of data has been written */ - private SignalConsumed mSignal = new SignalConsumed(); - - // inner classes - - /** - * Implements the http request - */ - class Connection implements Runnable { - public void run() { - boolean problem = false; - try { - if (Config.LOGV) { - Log.i(LOG_TAG, "REQUEST : " + mMethod.getRequestLine()); - } - mResponse = mClient.execute(mMethod); - if (mResponse != null) { - if (Config.LOGV) { - Log.i(LOG_TAG, "response (status line): " - + mResponse.getStatusLine()); - } - mResponseLine = "" + mResponse.getStatusLine(); - } else { - if (Config.LOGV) { - Log.i(LOG_TAG, "problem, response == null"); - } - problem = true; - } - } catch (IOException e) { - Log.e(LOG_TAG, "Connection IO exception ", e); - problem = true; - } catch (RuntimeException e) { - Log.e(LOG_TAG, "Connection runtime exception ", e); - problem = true; - } - - if (!problem) { - if (Config.LOGV) { - Log.i(LOG_TAG, "Request complete (" - + mMethod.getRequestLine() + ")"); - } - } else { - mConnectionFailedLock.lock(); - mConnectionFailed = true; - mConnectionFailedLock.unlock(); - if (Config.LOGV) { - Log.i(LOG_TAG, "Request FAILED (" - + mMethod.getRequestLine() + ")"); - } - // We abort the execution in order to shutdown and release - // the underlying connection - mMethod.abort(); - if (mPostEntity != null) { - // If there is a post entity, we need to wake it up from - // a potential deadlock - mPostEntity.signalOutputStream(); - } - } - } - } - - /** - * simple buffer class implementing a producer/consumer model - */ - class Buffer { - private DataPacket mPacket; - private boolean mEmpty = true; - public synchronized void put(DataPacket packet) { - while (!mEmpty) { - try { - wait(); - } catch (InterruptedException e) { - if (Config.LOGV) { - Log.i(LOG_TAG, "InterruptedException while putting " + - "a DataPacket in the Buffer: " + e); - } - } - } - mPacket = packet; - mEmpty = false; - notify(); - } - public synchronized DataPacket get() { - while (mEmpty) { - try { - wait(); - } catch (InterruptedException e) { - if (Config.LOGV) { - Log.i(LOG_TAG, "InterruptedException while getting " + - "a DataPacket in the Buffer: " + e); - } - } - } - mEmpty = true; - notify(); - return mPacket; - } - } - - /** - * utility class used to block until the packet is signaled as being - * consumed - */ - class SignalConsumed { - private boolean mConsumed = false; - public synchronized void waitUntilPacketConsumed() { - while (!mConsumed) { - try { - wait(); - } catch (InterruptedException e) { - if (Config.LOGV) { - Log.i(LOG_TAG, "InterruptedException while waiting " + - "until a DataPacket is consumed: " + e); - } - } - } - mConsumed = false; - notify(); - } - public synchronized void packetConsumed() { - while (mConsumed) { - try { - wait(); - } catch (InterruptedException e) { - if (Config.LOGV) { - Log.i(LOG_TAG, "InterruptedException while indicating " - + "that the DataPacket has been consumed: " + e); - } - } - } - mConsumed = true; - notify(); - } - } - - /** - * Utility class encapsulating a packet of data - */ - class DataPacket { - private byte[] mContent; - private int mLength; - public DataPacket(byte[] content, int length) { - mContent = content; - mLength = length; - } - public byte[] getBytes() { - return mContent; - } - public int getLength() { - return mLength; - } - } - - /** - * HttpEntity class to write the bytes received by the C++ thread - * on the connection outputstream, in a streaming way. - * This entity is executed in the request thread. - * The writeTo() method is automatically called by the - * HttpPost execution; upon reception, we loop while receiving - * the data packets from the main thread, until completion - * or error. When done, we flush the outputstream. - * The main thread (sendPostData()) also blocks until the - * outputstream is made available (or an error happens) - */ - class StreamEntity implements HttpEntity { - private OutputStream mOutputStream; - - // HttpEntity interface methods - - public boolean isRepeatable() { - return false; - } - - public boolean isChunked() { - return false; - } - - public long getContentLength() { - return mContentLength; - } - - public Header getContentType() { - return null; - } - - public Header getContentEncoding() { - return null; - } - - public InputStream getContent() throws IOException { - return null; - } - - public void writeTo(final OutputStream out) throws IOException { - // We signal that the outputstream is available - mStreamingReadyLock.lock(); - mOutputStream = out; - mStreamingReady.signal(); - mStreamingReadyLock.unlock(); - - // We then loop waiting on messages to process. - boolean finished = false; - while (!finished) { - DataPacket packet = mBuffer.get(); - if (packet == null) { - finished = true; - } else { - write(packet); - } - mSignal.packetConsumed(); - mConnectionFailedLock.lock(); - if (mConnectionFailed) { - if (Config.LOGV) { - Log.i(LOG_TAG, "stopping loop on error"); - } - finished = true; - } - mConnectionFailedLock.unlock(); - } - if (Config.LOGV) { - Log.i(LOG_TAG, "flushing the outputstream..."); - } - mOutputStream.flush(); - } - - public boolean isStreaming() { - return true; - } - - public void consumeContent() throws IOException { - // Nothing to release - } - - // local methods - - private void write(DataPacket packet) { - try { - if (mOutputStream == null) { - if (Config.LOGV) { - Log.i(LOG_TAG, "NO OUTPUT STREAM !!!"); - } - return; - } - mOutputStream.write(packet.getBytes(), 0, packet.getLength()); - mOutputStream.flush(); - } catch (IOException e) { - if (Config.LOGV) { - Log.i(LOG_TAG, "exc: " + e); - } - mConnectionFailedLock.lock(); - mConnectionFailed = true; - mConnectionFailedLock.unlock(); - } - } - - public boolean isReady() { - mStreamingReadyLock.lock(); - try { - if (mOutputStream == null) { - mStreamingReady.await(); - } - } catch (InterruptedException e) { - if (Config.LOGV) { - Log.i(LOG_TAG, "InterruptedException in " - + "StreamEntity::isReady() : ", e); - } - } finally { - mStreamingReadyLock.unlock(); - } - if (mOutputStream == null) { - return false; - } - return true; - } - - public void signalOutputStream() { - mStreamingReadyLock.lock(); - mStreamingReady.signal(); - mStreamingReadyLock.unlock(); - } - } - - /** - * Initialize mBridgeThread using the TLS value of - * Thread.currentThread(). Called on start up of the native child - * thread. - */ - public synchronized void initChildThread() { - mBridgeThread = Thread.currentThread(); - } - - public void setContentLength(long length) { - mContentLength = length; - } - - /** - * Analagous to the native-side HttpRequest::open() function. This - * initializes an underlying HttpClient method, but does - * not go to the wire. On success, this enables a call to send() to - * initiate the transaction. - * - * @param method The HTTP method, e.g GET or POST. - * @param url The URL to open. - * @return True on success with a complete HTTP response. - * False on failure. - */ - public synchronized boolean open(String method, String url) { - if (Config.LOGV) { - Log.i(LOG_TAG, "open " + method + " " + url); - } - // Create the client - if (mConnectionFailed) { - // interrupt() could have been called even before open() - return false; - } - mClient = new DefaultHttpClient(); - mClient.setHttpRequestRetryHandler( - new DefaultHttpRequestRetryHandler(0, false)); - mBodyInputStream = null; - mResponseLine = null; - mResponseHeaders = null; - mPostEntity = null; - mHttpThread = null; - mConnectionFailed = false; - mConnectionFinished = false; - - // Create the method. We support everything that - // Apache HttpClient supports, apart from TRACE. - if ("GET".equalsIgnoreCase(method)) { - mMethod = new HttpGet(url); - } else if ("POST".equalsIgnoreCase(method)) { - mMethod = new HttpPost(url); - mPostEntity = new StreamEntity(); - ((HttpPost)mMethod).setEntity(mPostEntity); - } else if ("HEAD".equalsIgnoreCase(method)) { - mMethod = new HttpHead(url); - } else if ("PUT".equalsIgnoreCase(method)) { - mMethod = new HttpPut(url); - } else if ("DELETE".equalsIgnoreCase(method)) { - mMethod = new HttpDelete(url); - } else { - if (Config.LOGV) { - Log.i(LOG_TAG, "Method " + method + " not supported"); - } - return false; - } - HttpParams params = mClient.getParams(); - // We handle the redirections C++-side - HttpClientParams.setRedirecting(params, false); - HttpProtocolParams.setUseExpectContinue(params, false); - return true; - } - - /** - * We use this to start the connection thread (doing the method execute). - * We usually always return true here, as the connection will run its - * course in the thread. - * We only return false if interrupted beforehand -- if a connection - * problem happens, we will thus fail in either sendPostData() or - * parseHeaders(). - */ - public synchronized boolean connectToRemote() { - boolean ret = false; - applyRequestHeaders(); - mConnectionFailedLock.lock(); - if (!mConnectionFailed) { - mHttpThread = new Thread(new Connection()); - mHttpThread.start(); - } - ret = mConnectionFailed; - mConnectionFailedLock.unlock(); - return !ret; - } - - /** - * Get the complete response line of the HTTP request. Only valid on - * completion of the transaction. - * @return The complete HTTP response line, e.g "HTTP/1.0 200 OK". - */ - public synchronized String getResponseLine() { - return mResponseLine; - } - - /** - * Wait for the request thread completion - * (unless already finished) - */ - private void waitUntilConnectionFinished() { - if (Config.LOGV) { - Log.i(LOG_TAG, "waitUntilConnectionFinished(" - + mConnectionFinished + ")"); - } - if (!mConnectionFinished) { - if (mHttpThread != null) { - try { - mHttpThread.join(); - mConnectionFinished = true; - if (Config.LOGV) { - Log.i(LOG_TAG, "http thread joined"); - } - } catch (InterruptedException e) { - if (Config.LOGV) { - Log.i(LOG_TAG, "interrupted: " + e); - } - } - } else { - Log.e(LOG_TAG, ">>> Trying to join on mHttpThread " + - "when it does not exist!"); - } - } - } - - // Headers handling - - /** - * Receive all headers from the server and populate - * mResponseHeaders. - * @return True if headers are successfully received, False on - * connection error. - */ - public synchronized boolean parseHeaders() { - mConnectionFailedLock.lock(); - if (mConnectionFailed) { - mConnectionFailedLock.unlock(); - return false; - } - mConnectionFailedLock.unlock(); - waitUntilConnectionFinished(); - mResponseHeaders = new HashMap<String, String[]>(); - if (mResponse == null) - return false; - - Header[] headers = mResponse.getAllHeaders(); - for (int i = 0; i < headers.length; i++) { - Header header = headers[i]; - if (Config.LOGV) { - Log.i(LOG_TAG, "header " + header.getName() - + " -> " + header.getValue()); - } - setResponseHeader(header.getName(), header.getValue()); - } - - return true; - } - - /** - * Set a header to send with the HTTP request. Will not take effect - * on a transaction already in progress. The key is associated - * case-insensitive, but stored case-sensitive. - * @param name The name of the header, e.g "Set-Cookie". - * @param value The value for this header, e.g "text/html". - */ - public synchronized void setRequestHeader(String name, String value) { - String[] mapValue = { name, value }; - if (Config.LOGV) { - Log.i(LOG_TAG, "setRequestHeader: " + name + " => " + value); - } - if (name.equalsIgnoreCase(KEY_CONTENT_LENGTH)) { - setContentLength(Long.parseLong(value)); - } else { - mRequestHeaders.put(name.toLowerCase(), mapValue); - } - } - - /** - * Returns the value associated with the given request header. - * @param name The name of the request header, non-null, case-insensitive. - * @return The value associated with the request header, or null if - * not set, or error. - */ - public synchronized String getRequestHeader(String name) { - String[] value = mRequestHeaders.get(name.toLowerCase()); - if (value != null) { - return value[HEADERS_MAP_INDEX_VALUE]; - } else { - return null; - } - } - - private void applyRequestHeaders() { - if (mMethod == null) - return; - Iterator<String[]> it = mRequestHeaders.values().iterator(); - while (it.hasNext()) { - // Set the key case-sensitive. - String[] entry = it.next(); - if (Config.LOGV) { - Log.i(LOG_TAG, "apply header " + entry[HEADERS_MAP_INDEX_KEY] + - " => " + entry[HEADERS_MAP_INDEX_VALUE]); - } - mMethod.setHeader(entry[HEADERS_MAP_INDEX_KEY], - entry[HEADERS_MAP_INDEX_VALUE]); - } - } - - /** - * Returns the value associated with the given response header. - * @param name The name of the response header, non-null, case-insensitive. - * @return The value associated with the response header, or null if - * not set or error. - */ - public synchronized String getResponseHeader(String name) { - if (mResponseHeaders != null) { - String[] value = mResponseHeaders.get(name.toLowerCase()); - if (value != null) { - return value[HEADERS_MAP_INDEX_VALUE]; - } else { - return null; - } - } else { - if (Config.LOGV) { - Log.i(LOG_TAG, "getResponseHeader() called but " - + "response not received"); - } - return null; - } - } - - /** - * Return all response headers, separated by CR-LF line endings, and - * ending with a trailing blank line. This mimics the format of the - * raw response header up to but not including the body. - * @return A string containing the entire response header. - */ - public synchronized String getAllResponseHeaders() { - if (mResponseHeaders == null) { - if (Config.LOGV) { - Log.i(LOG_TAG, "getAllResponseHeaders() called but " - + "response not received"); - } - return null; - } - StringBuilder result = new StringBuilder(); - Iterator<String[]> it = mResponseHeaders.values().iterator(); - while (it.hasNext()) { - String[] entry = it.next(); - // Output the "key: value" lines. - result.append(entry[HEADERS_MAP_INDEX_KEY]); - result.append(": "); - result.append(entry[HEADERS_MAP_INDEX_VALUE]); - result.append(HTTP_LINE_ENDING); - } - result.append(HTTP_LINE_ENDING); - return result.toString(); - } - - - /** - * Set a response header and associated value. The key is associated - * case-insensitively, but stored case-sensitively. - * @param name Case sensitive request header key. - * @param value The associated value. - */ - private void setResponseHeader(String name, String value) { - if (Config.LOGV) { - Log.i(LOG_TAG, "Set response header " + name + ": " + value); - } - String mapValue[] = { name, value }; - mResponseHeaders.put(name.toLowerCase(), mapValue); - } - - // Cookie handling - - /** - * Get the cookie for the given URL. - * @param url The fully qualified URL. - * @return A string containing the cookie for the URL if it exists, - * or null if not. - */ - public static String getCookieForUrl(String url) { - // Get the cookie for this URL, set as a header - return CookieManager.getInstance().getCookie(url); - } - - /** - * Set the cookie for the given URL. - * @param url The fully qualified URL. - * @param cookie The new cookie value. - * @return A string containing the cookie for the URL if it exists, - * or null if not. - */ - public static void setCookieForUrl(String url, String cookie) { - // Get the cookie for this URL, set as a header - CookieManager.getInstance().setCookie(url, cookie); - } - - // Cache handling - - /** - * Perform a request using LocalServer if possible. Initializes - * class members so that receive() will obtain data from the stream - * provided by the response. - * @param url The fully qualified URL to try in LocalServer. - * @return True if the url was found and is now setup to receive. - * False if not found, with no side-effect. - */ - public synchronized boolean useLocalServerResult(String url) { - UrlInterceptHandlerGears handler = - UrlInterceptHandlerGears.getInstance(); - if (handler == null) { - return false; - } - UrlInterceptHandlerGears.ServiceResponse serviceResponse = - handler.getServiceResponse(url, mRequestHeaders); - if (serviceResponse == null) { - if (Config.LOGV) { - Log.i(LOG_TAG, "No response in LocalServer"); - } - return false; - } - // LocalServer will handle this URL. Initialize stream and - // response. - mBodyInputStream = serviceResponse.getInputStream(); - mResponseLine = serviceResponse.getStatusLine(); - mResponseHeaders = serviceResponse.getResponseHeaders(); - if (Config.LOGV) { - Log.i(LOG_TAG, "Got response from LocalServer: " + mResponseLine); - } - return true; - } - - /** - * Perform a request using the cache result if present. Initializes - * class members so that receive() will obtain data from the cache. - * @param url The fully qualified URL to try in the cache. - * @return True is the url was found and is now setup to receive - * from cache. False if not found, with no side-effect. - */ - public synchronized boolean useCacheResult(String url) { - // Try the browser's cache. CacheManager wants a Map<String, String>. - Map<String, String> cacheRequestHeaders = new HashMap<String, String>(); - Iterator<Map.Entry<String, String[]>> it = - mRequestHeaders.entrySet().iterator(); - while (it.hasNext()) { - Map.Entry<String, String[]> entry = it.next(); - cacheRequestHeaders.put( - entry.getKey(), - entry.getValue()[HEADERS_MAP_INDEX_VALUE]); - } - CacheResult mCacheResult = - CacheManager.getCacheFile(url, cacheRequestHeaders); - if (mCacheResult == null) { - if (Config.LOGV) { - Log.i(LOG_TAG, "No CacheResult for " + url); - } - return false; - } - if (Config.LOGV) { - Log.i(LOG_TAG, "Got CacheResult from browser cache"); - } - // Check for expiry. -1 is "never", otherwise milliseconds since 1970. - // Can be compared to System.currentTimeMillis(). - long expires = mCacheResult.getExpires(); - if (expires >= 0 && System.currentTimeMillis() >= expires) { - if (Config.LOGV) { - Log.i(LOG_TAG, "CacheResult expired " - + (System.currentTimeMillis() - expires) - + " milliseconds ago"); - } - // Cache hit has expired. Do not return it. - return false; - } - // Setup the mBodyInputStream to come from the cache. - mBodyInputStream = mCacheResult.getInputStream(); - if (mBodyInputStream == null) { - // Cache result may have gone away. - if (Config.LOGV) { - Log.i(LOG_TAG, "No mBodyInputStream for CacheResult " + url); - } - return false; - } - // Cache hit. Parse headers. - synthesizeHeadersFromCacheResult(mCacheResult); - return true; - } - - /** - * Take the limited set of headers in a CacheResult and synthesize - * response headers. - * @param cacheResult A CacheResult to populate mResponseHeaders with. - */ - private void synthesizeHeadersFromCacheResult(CacheResult cacheResult) { - int statusCode = cacheResult.getHttpStatusCode(); - // The status message is informal, so we can greatly simplify it. - String statusMessage; - if (statusCode >= 200 && statusCode < 300) { - statusMessage = "OK"; - } else if (statusCode >= 300 && statusCode < 400) { - statusMessage = "MOVED"; - } else { - statusMessage = "UNAVAILABLE"; - } - // Synthesize the response line. - mResponseLine = "HTTP/1.1 " + statusCode + " " + statusMessage; - if (Config.LOGV) { - Log.i(LOG_TAG, "Synthesized " + mResponseLine); - } - // Synthesize the returned headers from cache. - mResponseHeaders = new HashMap<String, String[]>(); - String contentLength = Long.toString(cacheResult.getContentLength()); - setResponseHeader(KEY_CONTENT_LENGTH, contentLength); - long expires = cacheResult.getExpires(); - if (expires >= 0) { - // "Expires" header is valid and finite. Milliseconds since 1970 - // epoch, formatted as RFC-1123. - String expiresString = DateUtils.formatDate(new Date(expires)); - setResponseHeader(KEY_EXPIRES, expiresString); - } - String lastModified = cacheResult.getLastModified(); - if (lastModified != null) { - // Last modification time of the page. Passed end-to-end, but - // not used by us. - setResponseHeader(KEY_LAST_MODIFIED, lastModified); - } - String eTag = cacheResult.getETag(); - if (eTag != null) { - // Entity tag. A kind of GUID to identify identical resources. - setResponseHeader(KEY_ETAG, eTag); - } - String location = cacheResult.getLocation(); - if (location != null) { - // If valid, refers to the location of a redirect. - setResponseHeader(KEY_LOCATION, location); - } - String mimeType = cacheResult.getMimeType(); - if (mimeType == null) { - // Use a safe default MIME type when none is - // specified. "text/plain" is safe to render in the browser - // window (even if large) and won't be intepreted as anything - // that would cause execution. - mimeType = DEFAULT_MIME_TYPE; - } - String encoding = cacheResult.getEncoding(); - // Encoding may not be specified. No default. - String contentType = mimeType; - if (encoding != null) { - if (encoding.length() > 0) { - contentType += "; charset=" + encoding; - } - } - setResponseHeader(KEY_CONTENT_TYPE, contentType); - } - - /** - * Create a CacheResult for this URL. This enables the repsonse body - * to be sent in calls to appendCacheResult(). - * @param url The fully qualified URL to add to the cache. - * @param responseCode The response code returned for the request, e.g 200. - * @param mimeType The MIME type of the body, e.g "text/plain". - * @param encoding The encoding, e.g "utf-8". Use "" for unknown. - */ - public synchronized boolean createCacheResult( - String url, int responseCode, String mimeType, String encoding) { - if (Config.LOGV) { - Log.i(LOG_TAG, "Making cache entry for " + url); - } - // Take the headers and parse them into a format needed by - // CacheManager. - Headers cacheHeaders = new Headers(); - Iterator<Map.Entry<String, String[]>> it = - mResponseHeaders.entrySet().iterator(); - while (it.hasNext()) { - Map.Entry<String, String[]> entry = it.next(); - // Headers.parseHeader() expects lowercase keys. - String keyValue = entry.getKey() + ": " - + entry.getValue()[HEADERS_MAP_INDEX_VALUE]; - CharArrayBuffer buffer = new CharArrayBuffer(keyValue.length()); - buffer.append(keyValue); - // Parse it into the header container. - cacheHeaders.parseHeader(buffer); - } - mCacheResult = CacheManager.createCacheFile( - url, responseCode, cacheHeaders, mimeType, true); - if (mCacheResult != null) { - if (Config.LOGV) { - Log.i(LOG_TAG, "Saving into cache"); - } - mCacheResult.setEncoding(encoding); - mCacheResultUrl = url; - return true; - } else { - if (Config.LOGV) { - Log.i(LOG_TAG, "Couldn't create mCacheResult"); - } - return false; - } - } - - /** - * Add data from the response body to the CacheResult created with - * createCacheResult(). - * @param data A byte array of the next sequential bytes in the - * response body. - * @param bytes The number of bytes to write from the start of - * the array. - * @return True if all bytes successfully written, false on failure. - */ - public synchronized boolean appendCacheResult(byte[] data, int bytes) { - if (mCacheResult == null) { - if (Config.LOGV) { - Log.i(LOG_TAG, "appendCacheResult() called without a " - + "CacheResult initialized"); - } - return false; - } - try { - mCacheResult.getOutputStream().write(data, 0, bytes); - } catch (IOException ex) { - if (Config.LOGV) { - Log.i(LOG_TAG, "Got IOException writing cache data: " + ex); - } - return false; - } - return true; - } - - /** - * Save the completed CacheResult into the CacheManager. This must - * have been created first with createCacheResult(). - * @return Returns true if the entry has been successfully saved. - */ - public synchronized boolean saveCacheResult() { - if (mCacheResult == null || mCacheResultUrl == null) { - if (Config.LOGV) { - Log.i(LOG_TAG, "Tried to save cache result but " - + "createCacheResult not called"); - } - return false; - } - - if (Config.LOGV) { - Log.i(LOG_TAG, "Saving cache result"); - } - CacheManager.saveCacheFile(mCacheResultUrl, mCacheResult); - mCacheResult = null; - mCacheResultUrl = null; - return true; - } - - - /** - * Interrupt a blocking IO operation. This will cause the child - * thread to expediently return from an operation if it was stuck at - * the time. Note that this inherently races, and unfortunately - * requires the caller to loop. - */ - public synchronized void interrupt() { - if (Config.LOGV) { - Log.i(LOG_TAG, "INTERRUPT CALLED"); - } - mConnectionFailedLock.lock(); - mConnectionFailed = true; - mConnectionFailedLock.unlock(); - if (mMethod != null) { - mMethod.abort(); - } - if (mHttpThread != null) { - waitUntilConnectionFinished(); - } - } - - /** - * Receive the next sequential bytes of the response body after - * successful connection. This will receive up to the size of the - * provided byte array. If there is no body, this will return 0 - * bytes on the first call after connection. - * @param buf A pre-allocated byte array to receive data into. - * @return The number of bytes from the start of the array which - * have been filled, 0 on EOF, or negative on error. - */ - public synchronized int receive(byte[] buf) { - if (mBodyInputStream == null) { - // If this is the first call, setup the InputStream. This may - // fail if there were headers, but no body returned by the - // server. - try { - if (mResponse != null) { - HttpEntity entity = mResponse.getEntity(); - mBodyInputStream = entity.getContent(); - } - } catch (IOException inputException) { - if (Config.LOGV) { - Log.i(LOG_TAG, "Failed to connect InputStream: " - + inputException); - } - // Not unexpected. For example, 404 response return headers, - // and sometimes a body with a detailed error. - } - if (mBodyInputStream == null) { - // No error stream either. Treat as a 0 byte response. - if (Config.LOGV) { - Log.i(LOG_TAG, "No InputStream"); - } - return 0; // EOF. - } - } - int ret; - try { - int got = mBodyInputStream.read(buf); - if (got > 0) { - // Got some bytes, not EOF. - ret = got; - } else { - // EOF. - mBodyInputStream.close(); - ret = 0; - } - } catch (IOException e) { - // An abort() interrupts us by calling close() on our stream. - if (Config.LOGV) { - Log.i(LOG_TAG, "Got IOException in mBodyInputStream.read(): ", e); - } - ret = -1; - } - return ret; - } - - /** - * For POST method requests, send a stream of data provided by the - * native side in repeated callbacks. - * We put the data in mBuffer, and wait until it is consumed - * by the StreamEntity in the request thread. - * @param data A byte array containing the data to sent, or null - * if indicating EOF. - * @param bytes The number of bytes from the start of the array to - * send, or 0 if indicating EOF. - * @return True if all bytes were successfully sent, false on error. - */ - public boolean sendPostData(byte[] data, int bytes) { - mConnectionFailedLock.lock(); - if (mConnectionFailed) { - mConnectionFailedLock.unlock(); - return false; - } - mConnectionFailedLock.unlock(); - if (mPostEntity == null) return false; - - // We block until the outputstream is available - // (or in case of connection error) - if (!mPostEntity.isReady()) return false; - - if (data == null && bytes == 0) { - mBuffer.put(null); - } else { - mBuffer.put(new DataPacket(data, bytes)); - } - mSignal.waitUntilPacketConsumed(); - - mConnectionFailedLock.lock(); - if (mConnectionFailed) { - Log.e(LOG_TAG, "failure"); - mConnectionFailedLock.unlock(); - return false; - } - mConnectionFailedLock.unlock(); - return true; - } - -} diff --git a/core/java/android/webkit/gears/DesktopAndroid.java b/core/java/android/webkit/gears/DesktopAndroid.java deleted file mode 100644 index ee8ca49..0000000 --- a/core/java/android/webkit/gears/DesktopAndroid.java +++ /dev/null @@ -1,109 +0,0 @@ -// 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.content.ComponentName; -import android.content.Intent; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.net.Uri; -import android.util.Log; -import android.webkit.WebView; - -/** - * Utility class to create a shortcut on Android - */ -public class DesktopAndroid { - - private static final String TAG = "Gears-J-Desktop"; - private static final String EXTRA_SHORTCUT_DUPLICATE = "duplicate"; - private static final String ACTION_INSTALL_SHORTCUT = - "com.android.launcher.action.INSTALL_SHORTCUT"; - - // Android now enforces a 64x64 limit for the icon - private static int MAX_WIDTH = 64; - private static int MAX_HEIGHT = 64; - - /** - * Small utility function returning a Bitmap object. - * - * @param path the icon path - */ - private static Bitmap getBitmap(String path) { - return BitmapFactory.decodeFile(path); - } - - /** - * Create a shortcut for a webpage. - * - * <p>To set a shortcut on Android, we use the ACTION_INSTALL_SHORTCUT - * from the default Home application. We only have to create an Intent - * containing extra parameters specifying the shortcut. - * <p>Note: the shortcut mechanism is not system wide and depends on the - * Home application; if phone carriers decide to rewrite a Home application - * that does not accept this Intent, no shortcut will be added. - * - * @param webview the webview we are called from - * @param title the shortcut's title - * @param url the shortcut's url - * @param imagePath the local path of the shortcut's icon - */ - public static void setShortcut(WebView webview, String title, - String url, String imagePath) { - Context context = webview.getContext(); - - Intent viewWebPage = new Intent(Intent.ACTION_VIEW); - viewWebPage.setData(Uri.parse(url)); - viewWebPage.addCategory(Intent.CATEGORY_BROWSABLE); - - Intent intent = new Intent(ACTION_INSTALL_SHORTCUT); - intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, viewWebPage); - intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, title); - - // We disallow the creation of duplicate shortcuts (i.e. same - // url, same title, but different screen position). - intent.putExtra(EXTRA_SHORTCUT_DUPLICATE, false); - - Bitmap bmp = getBitmap(imagePath); - if (bmp != null) { - if ((bmp.getWidth() > MAX_WIDTH) || - (bmp.getHeight() > MAX_HEIGHT)) { - Bitmap scaledBitmap = Bitmap.createScaledBitmap(bmp, - MAX_WIDTH, MAX_HEIGHT, true); - intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, scaledBitmap); - } else { - intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, bmp); - } - } else { - // This should not happen as we just downloaded the icon - Log.e(TAG, "icon file <" + imagePath + "> not found"); - } - - context.sendBroadcast(intent); - } - -} diff --git a/core/java/android/webkit/gears/NativeDialog.java b/core/java/android/webkit/gears/NativeDialog.java deleted file mode 100644 index 9e2b375..0000000 --- a/core/java/android/webkit/gears/NativeDialog.java +++ /dev/null @@ -1,142 +0,0 @@ -// 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 deleted file mode 100644 index 2d0cc13..0000000 --- a/core/java/android/webkit/gears/PluginSettings.java +++ /dev/null @@ -1,79 +0,0 @@ -// 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 deleted file mode 100644 index 2a5cbe9..0000000 --- a/core/java/android/webkit/gears/UrlInterceptHandlerGears.java +++ /dev/null @@ -1,501 +0,0 @@ -// 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.net.http.Headers; -import android.util.Log; -import android.webkit.CacheManager; -import android.webkit.CacheManager.CacheResult; -import android.webkit.Plugin; -import android.webkit.UrlInterceptRegistry; -import android.webkit.UrlInterceptHandler; -import android.webkit.WebView; - -import org.apache.http.impl.cookie.DateUtils; -import org.apache.http.util.CharArrayBuffer; - -import java.io.*; -import java.util.*; - -/** - * Services requests to handle URLs coming from the browser or - * HttpRequestAndroid. This registers itself with the - * UrlInterceptRegister in Android so we get a chance to service all - * URLs passing through the browser before anything else. - */ -public class UrlInterceptHandlerGears implements UrlInterceptHandler { - /** Singleton instance. */ - private static UrlInterceptHandlerGears instance; - /** Debug logging tag. */ - private static final String LOG_TAG = "Gears-J"; - /** Buffer size for reading/writing streams. */ - private static final int BUFFER_SIZE = 4096; - /** - * Number of milliseconds to expire LocalServer temporary entries in - * the browser's cache. Somewhat arbitrarily chosen as a compromise - * between being a) long enough not to expire during page load and - * b) short enough to evict entries during a session. */ - private static final int CACHE_EXPIRY_MS = 60000; // 1 minute. - /** Enable/disable all logging in this class. */ - private static boolean logEnabled = false; - /** The unmodified (case-sensitive) key in the headers map is the - * same index as used by HttpRequestAndroid. */ - public static final int HEADERS_MAP_INDEX_KEY = - ApacheHttpRequestAndroid.HEADERS_MAP_INDEX_KEY; - /** The associated value in the headers map is the same index as - * used by HttpRequestAndroid. */ - public static final int HEADERS_MAP_INDEX_VALUE = - ApacheHttpRequestAndroid.HEADERS_MAP_INDEX_VALUE; - - /** - * Object passed to the native side, containing information about - * the URL to service. - */ - public static class ServiceRequest { - // The URL being requested. - private String url; - // Request headers. Map of lowercase key to [ unmodified key, value ]. - private Map<String, String[]> requestHeaders; - - /** - * Initialize members on construction. - * @param url The URL being requested. - * @param requestHeaders Headers associated with the request, - * or null if none. - * Map of lowercase key to [ unmodified key, value ]. - */ - public ServiceRequest(String url, Map<String, String[]> requestHeaders) { - this.url = url; - this.requestHeaders = requestHeaders; - } - - /** - * Returns the URL being requested. - * @return The URL being requested. - */ - public String getUrl() { - return url; - } - - /** - * Get the value associated with a request header key, if any. - * @param header The key to find, case insensitive. - * @return The value associated with this header, or null if not found. - */ - public String getRequestHeader(String header) { - if (requestHeaders != null) { - String[] value = requestHeaders.get(header.toLowerCase()); - if (value != null) { - return value[HEADERS_MAP_INDEX_VALUE]; - } else { - return null; - } - } else { - return null; - } - } - } - - /** - * Object returned by the native side, containing information needed - * to pass the entire response back to the browser or - * HttpRequestAndroid. Works from either an in-memory array or a - * file on disk. - */ - public class ServiceResponse { - // The response status code, e.g 200. - private int statusCode; - // The full status line, e.g "HTTP/1.1 200 OK". - private String statusLine; - // All headers associated with the response. Map of lowercase key - // to [ unmodified key, value ]. - private Map<String, String[]> responseHeaders = - new HashMap<String, String[]>(); - // The MIME type, e.g "text/html". - private String mimeType; - // The encoding, e.g "utf-8", or null if none. - private String encoding; - // The stream which contains the body when read(). - private InputStream inputStream; - - /** - * Initialize members using an in-memory array to return the body. - * @param statusCode The response status code, e.g 200. - * @param statusLine The full status line, e.g "HTTP/1.1 200 OK". - * @param mimeType The MIME type, e.g "text/html". - * @param encoding Encoding, e.g "utf-8" or null if none. - * @param body The response body as a byte array, non-empty. - */ - void setResultArray( - int statusCode, - String statusLine, - String mimeType, - String encoding, - byte[] body) { - this.statusCode = statusCode; - this.statusLine = statusLine; - this.mimeType = mimeType; - this.encoding = encoding; - // Setup a stream to read out of the byte array. - this.inputStream = new ByteArrayInputStream(body); - } - - /** - * Initialize members using a file on disk to return the body. - * @param statusCode The response status code, e.g 200. - * @param statusLine The full status line, e.g "HTTP/1.1 200 OK". - * @param mimeType The MIME type, e.g "text/html". - * @param encoding Encoding, e.g "utf-8" or null if none. - * @param path Full path to the file containing the body. - * @return True if the file is successfully setup to stream, - * false on error such as file not found. - */ - boolean setResultFile( - int statusCode, - String statusLine, - String mimeType, - String encoding, - String path) { - this.statusCode = statusCode; - this.statusLine = statusLine; - this.mimeType = mimeType; - this.encoding = encoding; - try { - // Setup a stream to read out of a file on disk. - this.inputStream = new FileInputStream(new File(path)); - return true; - } catch (java.io.FileNotFoundException ex) { - log("File not found: " + path); - return false; - } - } - - /** - * Set a response header, adding its settings to the header members. - * @param key The case sensitive key for the response header, - * e.g "Set-Cookie". - * @param value The value associated with this key, e.g "cookie1234". - */ - public void setResponseHeader(String key, String value) { - // The map value contains the unmodified key (not lowercase). - String[] mapValue = { key, value }; - responseHeaders.put(key.toLowerCase(), mapValue); - } - - /** - * Return the "Content-Type" header possibly supplied by a - * previous setResponseHeader(). - * @return The "Content-Type" value, or null if not present. - */ - public String getContentType() { - // The map keys are lowercase. - String[] value = responseHeaders.get("content-type"); - if (value != null) { - return value[HEADERS_MAP_INDEX_VALUE]; - } else { - return null; - } - } - - /** - * Returns the HTTP status code for the response, supplied in - * setResultArray() or setResultFile(). - * @return The HTTP statue code, e.g 200. - */ - public int getStatusCode() { - return statusCode; - } - - /** - * Returns the full HTTP status line for the response, supplied in - * setResultArray() or setResultFile(). - * @return The HTTP statue line, e.g "HTTP/1.1 200 OK". - */ - public String getStatusLine() { - return statusLine; - } - - /** - * Get all response headers supplied in calls in - * setResponseHeader(). - * @return A Map<String, String[]> containing all headers. - */ - public Map<String, String[]> getResponseHeaders() { - return responseHeaders; - } - - /** - * Returns the MIME type for the response, supplied in - * setResultArray() or setResultFile(). - * @return The MIME type, e.g "text/html". - */ - public String getMimeType() { - return mimeType; - } - - /** - * Returns the encoding for the response, supplied in - * setResultArray() or setResultFile(), or null if none. - * @return The encoding, e.g "utf-8", or null if none. - */ - public String getEncoding() { - return encoding; - } - - /** - * Returns the InputStream setup by setResultArray() or - * setResultFile() to allow reading data either from memory or - * disk. - * @return The InputStream containing the response body. - */ - public InputStream getInputStream() { - return inputStream; - } - } - - /** - * Construct and initialize the singleton instance. - */ - public UrlInterceptHandlerGears() { - if (instance != null) { - Log.e(LOG_TAG, "UrlInterceptHandlerGears singleton already constructed"); - throw new RuntimeException(); - } - instance = this; - } - - /** - * Turn on/off logging in this class. - * @param on Logging enable state. - */ - public static void enableLogging(boolean on) { - logEnabled = on; - } - - /** - * Get the singleton instance. - * @return The singleton instance. - */ - public static UrlInterceptHandlerGears getInstance() { - return instance; - } - - /** - * Register the singleton instance with the browser's interception - * mechanism. - */ - public synchronized void register() { - UrlInterceptRegistry.registerHandler(this); - } - - /** - * Unregister the singleton instance from the browser's interception - * mechanism. - */ - public synchronized void unregister() { - UrlInterceptRegistry.unregisterHandler(this); - } - - /** - * Copy the entire InputStream to OutputStream. - * @param inputStream The stream to read from. - * @param outputStream The stream to write to. - * @return True if the entire stream copied successfully, false on error. - */ - private boolean copyStream(InputStream inputStream, - OutputStream outputStream) { - try { - // Temporary buffer to copy stream through. - byte[] buf = new byte[BUFFER_SIZE]; - for (;;) { - // Read up to BUFFER_SIZE bytes. - int bytes = inputStream.read(buf); - if (bytes < 0) { - break; - } - // Write the number of bytes we just read. - outputStream.write(buf, 0, bytes); - } - } catch (IOException ex) { - log("Got IOException copying stream: " + ex); - return false; - } - return true; - } - - /** - * Given an URL, returns a CacheResult which contains the response - * for the request. This implements the UrlInterceptHandler interface. - * - * @param url The fully qualified URL being requested. - * @param requestHeaders The request headers for this URL. - * @return If a response can be crafted, a CacheResult initialized - * to return the surrogate response. If this URL cannot - * be serviced, returns null. - */ - public CacheResult service(String url, Map<String, String> requestHeaders) { - // Thankfully the browser does call us with case-sensitive - // headers. We just need to map it case-insensitive. - Map<String, String[]> lowercaseRequestHeaders = - new HashMap<String, String[]>(); - Iterator<Map.Entry<String, String>> requestHeadersIt = - requestHeaders.entrySet().iterator(); - while (requestHeadersIt.hasNext()) { - Map.Entry<String, String> entry = requestHeadersIt.next(); - String key = entry.getKey(); - String mapValue[] = { key, entry.getValue() }; - lowercaseRequestHeaders.put(key.toLowerCase(), mapValue); - } - ServiceResponse response = getServiceResponse(url, lowercaseRequestHeaders); - if (response == null) { - // No result for this URL. - return null; - } - // Translate the ServiceResponse to a CacheResult. - // Translate http -> gears, https -> gearss, so we don't overwrite - // existing entries. - String gearsUrl = "gears" + url.substring("http".length()); - // Set the result to expire, so that entries don't pollute the - // browser's cache for too long. - long now_ms = System.currentTimeMillis(); - String expires = DateUtils.formatDate(new Date(now_ms + CACHE_EXPIRY_MS)); - response.setResponseHeader(ApacheHttpRequestAndroid.KEY_EXPIRES, expires); - // The browser is only interested in a small subset of headers, - // contained in a Headers object. Iterate the map of all headers - // and add them to Headers. - Headers headers = new Headers(); - Iterator<Map.Entry<String, String[]>> responseHeadersIt = - response.getResponseHeaders().entrySet().iterator(); - while (responseHeadersIt.hasNext()) { - Map.Entry<String, String[]> entry = responseHeadersIt.next(); - // Headers.parseHeader() expects lowercase keys. - String keyValue = entry.getKey() + ": " - + entry.getValue()[HEADERS_MAP_INDEX_VALUE]; - CharArrayBuffer buffer = new CharArrayBuffer(keyValue.length()); - buffer.append(keyValue); - // Parse it into the header container. - headers.parseHeader(buffer); - } - CacheResult cacheResult = CacheManager.createCacheFile( - gearsUrl, - response.getStatusCode(), - headers, - response.getMimeType(), - true); // forceCache - - if (cacheResult == null) { - // With the no-cache policy we could end up - // with a null result - return null; - } - - // Set encoding if specified. - String encoding = response.getEncoding(); - if (encoding != null) { - cacheResult.setEncoding(encoding); - } - // Copy the response body to the CacheResult. This handles all - // combinations of memory vs on-disk on both sides. - InputStream inputStream = response.getInputStream(); - OutputStream outputStream = cacheResult.getOutputStream(); - boolean copied = copyStream(inputStream, outputStream); - // Close the input and output streams to relinquish their - // resources earlier. - try { - inputStream.close(); - } catch (IOException ex) { - log("IOException closing InputStream: " + ex); - copied = false; - } - try { - outputStream.close(); - } catch (IOException ex) { - log("IOException closing OutputStream: " + ex); - copied = false; - } - if (!copied) { - log("copyStream of local result failed"); - return null; - } - // Save the entry into the browser's cache. - CacheManager.saveCacheFile(gearsUrl, cacheResult); - // Get it back from the cache, this time properly initialized to - // be used for input. - cacheResult = CacheManager.getCacheFile(gearsUrl, null); - if (cacheResult != null) { - log("Returning surrogate result"); - return cacheResult; - } else { - // Not an expected condition, but handle gracefully. Perhaps out - // of memory or disk? - Log.e(LOG_TAG, "Lost CacheResult between save and get. Can't serve.\n"); - return null; - } - } - - /** - * Given an URL, returns a CacheResult and headers which contain the - * response for the request. - * - * @param url The fully qualified URL being requested. - * @param requestHeaders The request headers for this URL. - * @return If a response can be crafted, a ServiceResponse is - * created which contains all response headers and an InputStream - * attached to the body. If there is no response, null is returned. - */ - public ServiceResponse getServiceResponse(String url, - Map<String, String[]> requestHeaders) { - if (!url.startsWith("http://") && !url.startsWith("https://")) { - // Don't know how to service non-HTTP URLs - return null; - } - // Call the native handler to craft a response for this URL. - return nativeService(new ServiceRequest(url, requestHeaders)); - } - - /** - * 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) { - if (logEnabled) { - Log.i(LOG_TAG, str); - } - } - - /** - * Native method which handles the bulk of the request in LocalServer. - * @param request A ServiceRequest object containing information about - * the request. - * @return If serviced, a ServiceResponse object containing all the - * information to provide a response for the URL, or null - * if no response available for this URL. - */ - private native static ServiceResponse nativeService(ServiceRequest request); -} diff --git a/core/java/android/webkit/gears/VersionExtractor.java b/core/java/android/webkit/gears/VersionExtractor.java deleted file mode 100644 index 172dacb..0000000 --- a/core/java/android/webkit/gears/VersionExtractor.java +++ /dev/null @@ -1,147 +0,0 @@ -// 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.util.Log; -import java.io.IOException; -import java.io.StringReader; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.FactoryConfigurationError; -import javax.xml.parsers.ParserConfigurationException; -import org.xml.sax.InputSource; -import org.xml.sax.SAXException; -import org.w3c.dom.Document; -import org.w3c.dom.DOMException; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; - -/** - * A class that can extract the Gears version and upgrade URL from an - * xml document. - */ -public final class VersionExtractor { - - /** - * Logging tag - */ - private static final String TAG = "Gears-J-VersionExtractor"; - /** - * XML element names. - */ - private static final String VERSION = "em:version"; - private static final String URL = "em:updateLink"; - - /** - * Parses the input xml string and invokes the native - * setVersionAndUrl method. - * @param xml is the XML string to parse. - * @return true if the extraction is successful and false otherwise. - */ - public static boolean extract(String xml, long nativeObject) { - try { - // Build the builders. - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - factory.setNamespaceAware(false); - DocumentBuilder builder = factory.newDocumentBuilder(); - - // Create the document. - Document doc = builder.parse(new InputSource(new StringReader(xml))); - - // Look for the version and url elements and get their text - // contents. - String version = extractText(doc, VERSION); - String url = extractText(doc, URL); - - // If we have both, let the native side know. - if (version != null && url != null) { - setVersionAndUrl(version, url, nativeObject); - return true; - } - - return false; - - } catch (FactoryConfigurationError ex) { - Log.e(TAG, "Could not create the DocumentBuilderFactory " + ex); - } catch (ParserConfigurationException ex) { - Log.e(TAG, "Could not create the DocumentBuilder " + ex); - } catch (SAXException ex) { - Log.e(TAG, "Could not parse the xml " + ex); - } catch (IOException ex) { - Log.e(TAG, "Could not read the xml " + ex); - } - - return false; - } - - /** - * Extracts the text content of the first element with the given name. - * @param doc is the Document where the element is searched for. - * @param elementName is name of the element to searched for. - * @return the text content of the element or null if no such - * element is found. - */ - private static String extractText(Document doc, String elementName) { - String text = null; - NodeList node_list = doc.getElementsByTagName(elementName); - - if (node_list.getLength() > 0) { - // We are only interested in the first node. Normally there - // should not be more than one anyway. - Node node = node_list.item(0); - - // Iterate through the text children. - NodeList child_list = node.getChildNodes(); - - try { - for (int i = 0; i < child_list.getLength(); ++i) { - Node child = child_list.item(i); - if (child.getNodeType() == Node.TEXT_NODE) { - if (text == null) { - text = new String(); - } - text += child.getNodeValue(); - } - } - } catch (DOMException ex) { - Log.e(TAG, "getNodeValue() failed " + ex); - } - } - - if (text != null) { - text = text.trim(); - } - - return text; - } - - /** - * Native method used to send the version and url back to the C++ - * side. - */ - private static native void setVersionAndUrl( - String version, String url, long nativeObject); -} diff --git a/core/java/android/webkit/gears/ZipInflater.java b/core/java/android/webkit/gears/ZipInflater.java deleted file mode 100644 index f6b6be5..0000000 --- a/core/java/android/webkit/gears/ZipInflater.java +++ /dev/null @@ -1,200 +0,0 @@ -// 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.os.StatFs; -import android.util.Log; -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.util.Enumeration; -import java.util.zip.CRC32; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; -import java.util.zip.ZipInputStream; - - -/** - * A class that can inflate a zip archive. - */ -public final class ZipInflater { - /** - * Logging tag - */ - private static final String TAG = "Gears-J-ZipInflater"; - - /** - * The size of the buffer used to read from the archive. - */ - private static final int BUFFER_SIZE_BYTES = 32 * 1024; // 32 KB. - /** - * The path navigation component (i.e. "../"). - */ - private static final String PATH_NAVIGATION_COMPONENT = ".." + File.separator; - /** - * The root of the data partition. - */ - private static final String DATA_PARTITION_ROOT = "/data"; - - /** - * We need two be able to store two versions of gears in parallel: - * - the zipped version - * - the unzipped version, which will be loaded next time the browser is started. - * We are conservative and do not attempt to unpack unless there enough free - * space on the device to store 4 times the new Gears size. - */ - private static final long SIZE_MULTIPLIER = 4; - - /** - * Unzips the archive with the given name. - * @param filename is the name of the zip to inflate. - * @param path is the path where the zip should be unpacked. It must contain - * a trailing separator, or the extraction will fail. - * @return true if the extraction is successful and false otherwise. - */ - public static boolean inflate(String filename, String path) { - Log.i(TAG, "Extracting " + filename + " to " + path); - - // Check that the path ends with a separator. - if (!path.endsWith(File.separator)) { - Log.e(TAG, "Path missing trailing separator: " + path); - return false; - } - - boolean result = false; - - // Use a ZipFile to get an enumeration of the entries and - // calculate the overall uncompressed size of the archive. Also - // check for existing files or directories that have the same - // name as the entries in the archive. Also check for invalid - // entry names (e.g names that attempt to navigate to the - // parent directory). - ZipInputStream zipStream = null; - long uncompressedSize = 0; - try { - ZipFile zipFile = new ZipFile(filename); - try { - Enumeration entries = zipFile.entries(); - while (entries.hasMoreElements()) { - ZipEntry entry = (ZipEntry) entries.nextElement(); - uncompressedSize += entry.getSize(); - // Check against entry names that may attempt to navigate - // out of the destination directory. - if (entry.getName().indexOf(PATH_NAVIGATION_COMPONENT) >= 0) { - throw new IOException("Illegal entry name: " + entry.getName()); - } - - // Check against entries with the same name as pre-existing files or - // directories. - File file = new File(path + entry.getName()); - if (file.exists()) { - // A file or directory with the same name already exist. - // This must not happen, so we treat this as an error. - throw new IOException( - "A file or directory with the same name already exists."); - } - } - } finally { - zipFile.close(); - } - - Log.i(TAG, "Determined uncompressed size: " + uncompressedSize); - // Check we have enough space to unpack this archive. - if (freeSpace() <= uncompressedSize * SIZE_MULTIPLIER) { - throw new IOException("Not enough space to unpack this archive."); - } - - zipStream = new ZipInputStream( - new BufferedInputStream(new FileInputStream(filename))); - ZipEntry entry; - int counter; - byte buffer[] = new byte[BUFFER_SIZE_BYTES]; - - // Iterate through the entries and write each of them to a file. - while ((entry = zipStream.getNextEntry()) != null) { - File file = new File(path + entry.getName()); - if (entry.isDirectory()) { - // If the entry denotes a directory, we need to create a - // directory with the same name. - file.mkdirs(); - } else { - CRC32 checksum = new CRC32(); - BufferedOutputStream output = new BufferedOutputStream( - new FileOutputStream(file), - BUFFER_SIZE_BYTES); - try { - // Read the entry and write it to the file. - while ((counter = zipStream.read(buffer, 0, BUFFER_SIZE_BYTES)) != - -1) { - output.write(buffer, 0, counter); - checksum.update(buffer, 0, counter); - } - output.flush(); - } finally { - output.close(); - } - - if (checksum.getValue() != entry.getCrc()) { - throw new IOException( - "Integrity check failed for: " + entry.getName()); - } - } - zipStream.closeEntry(); - } - - result = true; - - } catch (FileNotFoundException ex) { - Log.e(TAG, "The zip file could not be found. " + ex); - } catch (IOException ex) { - Log.e(TAG, "Could not read or write an entry. " + ex); - } catch(IllegalArgumentException ex) { - Log.e(TAG, "Could not create the BufferedOutputStream. " + ex); - } finally { - if (zipStream != null) { - try { - zipStream.close(); - } catch (IOException ex) { - // Ignored. - } - } - // Discard any exceptions and return the result to the native side. - return result; - } - } - - private static final long freeSpace() { - StatFs data_partition = new StatFs(DATA_PARTITION_ROOT); - long freeSpace = data_partition.getAvailableBlocks() * - data_partition.getBlockSize(); - Log.i(TAG, "Free space on the data partition: " + freeSpace); - return freeSpace; - } -} diff --git a/core/java/android/webkit/gears/package.html b/core/java/android/webkit/gears/package.html deleted file mode 100644 index db6f78b..0000000 --- a/core/java/android/webkit/gears/package.html +++ /dev/null @@ -1,3 +0,0 @@ -<body> -{@hide} -</body>
\ No newline at end of file diff --git a/core/java/android/webkit/package.html b/core/java/android/webkit/package.html deleted file mode 100644 index 4ed08da..0000000 --- a/core/java/android/webkit/package.html +++ /dev/null @@ -1,7 +0,0 @@ -<html> -<body> -Provides tools for browsing the web. -<p>The only classes or interfaces in this package intended for use by SDK -developers are WebView, BroswerCallbackAdapter, BrowserCallback, and CookieManager. -</body> -</html>
\ No newline at end of file |