FileDocCategorySizeDatePackage
LoadListener.javaAPI DocAndroid 1.5 API54751Wed May 06 22:41:56 BST 2009android.webkit

LoadListener

public class LoadListener extends android.os.Handler implements android.net.http.EventHandler

Fields Summary
private static final String
LOGTAG
private static final int
MSG_CONTENT_HEADERS
private static final int
MSG_CONTENT_DATA
private static final int
MSG_CONTENT_FINISHED
private static final int
MSG_CONTENT_ERROR
private static final int
MSG_LOCATION_CHANGED
private static final int
MSG_LOCATION_CHANGED_REQUEST
private static final int
MSG_STATUS
private static final int
MSG_SSL_CERTIFICATE
private static final int
MSG_SSL_ERROR
private static final int
HTTP_OK
private static final int
HTTP_MOVED_PERMANENTLY
private static final int
HTTP_FOUND
private static final int
HTTP_SEE_OTHER
private static final int
HTTP_NOT_MODIFIED
private static final int
HTTP_TEMPORARY_REDIRECT
private static final int
HTTP_AUTH
private static final int
HTTP_NOT_FOUND
private static final int
HTTP_PROXY_AUTH
private static int
sNativeLoaderCount
private final ByteArrayBuilder
mDataBuilder
private String
mUrl
private android.net.WebAddress
mUri
private boolean
mPermanent
private String
mOriginalUrl
private android.content.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
private boolean
mCancelled
private boolean
mAuthFailed
private CacheLoader
mCacheLoader
private android.webkit.CacheManager.CacheResult
mCacheResult
private android.net.http.HttpAuthHeader
mAuthHeader
private int
mErrorID
private String
mErrorDescription
private android.net.http.SslError
mSslError
private android.net.http.RequestHandle
mRequestHandle
private String
mMethod
private Map
mRequestHeaders
private byte[]
mPostData
private boolean
mIsHighPriority
private boolean
mSynchronous
private Vector
mMessageQueue
private boolean
mIsMainPageLoader
private android.net.http.Headers
mHeaders
private int
mCacheRedirectCount
private static final Pattern
CONTENT_TYPE_PATTERN
Parses the content-type header. The first part only allows '-' if it follows x or X.
Constructors Summary
LoadListener(android.content.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;
    
Methods Summary
voidattachRequestHandle(android.net.http.RequestHandle requestHandle)

        if (Config.LOGV) {
            Log.v(LOGTAG, "LoadListener.attachRequestHandle(): " +
                    "requestHandle: " +  requestHandle);
        }
        mRequestHandle = requestHandle;
    
booleanauthCredentialsInvalid()
Returns true iff an HTTP authentication problem has occured (credentials invalid).

        // if it is digest and the nonce is stale, we just
        // resubmit with a new nonce
        return (mAuthFailed &&
                !(mAuthHeader.isDigest() && mAuthHeader.getStale()));
    
public voidcancel()
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.

        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();
    
public booleancancelled()

return
True iff the load has been cancelled

        return mCancelled;
    
public voidcertificate(android.net.http.SslCertificate certificate)
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

        sendMessageInternal(obtainMessage(MSG_SSL_CERTIFICATE, certificate));
    
booleancheckCache(java.util.Map headers)
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.

        // 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;
    
private voidclearNativeLoader()
We keep a count of refs to the nativeLoader so we do not create so many LoadListeners that the GREFs blow up

        sNativeLoaderCount -= 1;
        mNativeLoader = 0;
    
private voidcommitHeaders()

        // Commit the headers to WebCore
        int nativeResponse = createNativeResponse();
        // The native code deletes the native response object.
        nativeReceivedResponse(nativeResponse);
    
private voidcommitHeadersCheckRedirect()

        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();
    
private voidcommitLoad()
Commit the load. It should be ok to call repeatedly but only before tearDown is called.

        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");
        }
    
longcontentLength()

        return mContentLength;
    
private intcreateNativeResponse()
Create a WebCore response object so that it can be used by nativeReceivedResponse or nativeRedirectedToUrl

return
native response pointer

        // If WebCore sends if-modified-since, mCacheLoader is null. If 
        // CacheManager sends it, mCacheLoader is not null. In this case, 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 &&
                mCacheLoader != null) ? 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;
    
public voiddata(byte[] data, int length)
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.

        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));
        }
    
voiddetachRequestHandle()

        if (Config.LOGV) {
            Log.v(LOGTAG, "LoadListener.detachRequestHandle(): " +
                    "requestHandle: " + mRequestHandle);
        }
        mRequestHandle = null;
    
private voiddoRedirect()

        // 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;
            }

            // This will strip the anchor
            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(mUrl, 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;
            }
        } else {
            commitHeaders();
            commitLoad();
            tearDown();
        }

        if (Config.LOGV) {
            Log.v(LOGTAG, "LoadListener.onRedirect(): redirect to: " +
                    redirectTo);
        }
    
voiddownloadFile()

        // 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();
    
public voidendData()
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

        if (Config.LOGV) {
            Log.v(LOGTAG, "LoadListener.endData(): url: " + url());
        }
        sendMessageInternal(obtainMessage(MSG_CONTENT_FINISHED));
    
public voiderror(int id, java.lang.String description)
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

        if (Config.LOGV) {
            Log.v(LOGTAG, "LoadListener.error url:" +
                    url() + " id:" + id + " description:" + description);
        }
        sendMessageInternal(obtainMessage(MSG_CONTENT_ERROR, id, 0, description));
    
android.content.ContextgetContext()

        return mContext;
    
private java.lang.StringgetErrorDescription()
Return the error description.

return
errorDescription.

        return mErrorDescription;
    
private intgetErrorID()
Helper for getting the error ID.

return
errorID.

        return mErrorID;
    
BrowserFramegetFrame()

return
The loader's BrowserFrame.

        return mBrowserFrame;
    
public static android.webkit.LoadListenergetLoadListener(android.content.Context context, BrowserFrame frame, java.lang.String url, int nativeLoader, boolean synchronous, boolean isMainPageLoader)


    // =========================================================================
    // Public functions
    // =========================================================================

       
                 
                  

        sNativeLoaderCount += 1;
        return new LoadListener(
            context, frame, url, nativeLoader, synchronous, isMainPageLoader);
    
public static intgetNativeLoaderCount()

        return sNativeLoaderCount;
    
android.net.WebAddressgetWebAddress()

return
The current WebAddress associated with this load.

        return mUri;
    
private voidguessMimeType()
Guesses MIME type if one was not specified. Defaults to 'text/html'. In addition, tries to guess the MIME type based on the extension.

        // 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;
            }
        }
    
private java.lang.StringguessMimeTypeFromExtension()
guess MIME type based on the file extension.

        // 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;
    
voidhandleAuthResponse(java.lang.String username, java.lang.String password)
Uses user-supplied credentials to restart a request. If the credentials are null, cancel the request.

        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);
                    }
                }
            }
        } else {
            // Commit whatever data we have and tear down the loader.
            commitLoad();
            tearDown();
        }
    
private voidhandleCertificate(android.net.http.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);
        }
    
private voidhandleEndData()

        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) {
                    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();
    
private voidhandleError(int id, java.lang.String description)

        mErrorID = id;
        mErrorDescription = description;
        detachRequestHandle();
        notifyError();
        tearDown();
    
private voidhandleHeaders(android.net.http.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();
    
public voidhandleMessage(android.os.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;
        }
    
private voidhandleSslError(android.net.http.SslError error)

        if (!mCancelled) {
            mSslError = error;
            Network.getInstance(mContext).handleSslErrorRequest(this);
        }
    
public voidhandleSslErrorRequest(android.net.http.SslError error)
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

        if (Config.LOGV) {
            Log.v(LOGTAG,
                    "LoadListener.handleSslErrorRequest(): url:" + url() +
                    " primary error: " + error.getPrimaryError() +
                    " certificate: " + error.getCertificate());
        }
        sendMessageInternal(obtainMessage(MSG_SSL_ERROR, error));
    
voidhandleSslErrorResponse(boolean proceed)
Handles SSL error(s) on the way down from the user (the user has already provided their feedback).

        if (mRequestHandle != null) {
            mRequestHandle.handleSslErrorResponse(proceed);
        }
        if (!proceed) {
            // Commit whatever data we have and tear down the loader.
            commitLoad();
            tearDown();
        }
    
private voidhandleStatus(int major, int minor, int code, java.lang.String reason)

        if (mCancelled) return;

        mStatusCode = code;
        mStatusText = reason;
        mPermanent = false;
    
public voidheaders(android.net.http.Headers headers)
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

        if (Config.LOGV) Log.v(LOGTAG, "LoadListener.headers");
        sendMessageInternal(obtainMessage(MSG_CONTENT_HEADERS, headers));
    
java.lang.Stringhost()

return
URL hostname (current URL).

        if (mUri != null) {
            return mUri.mHost;
        }

        return null;
    
private booleanignoreCallbacks()
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.

        return (mCancelled || mAuthHeader != null ||
                (mStatusCode > 300 && mStatusCode < 400));
    
booleanisSynchronous()

        return mSynchronous;
    
voidloadSynchronousMessages()
Cycle through our messages for synchronous loads.

        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));
        }
    
java.lang.StringmimeType()

        return mMimeType;
    
private native voidnativeAddData(byte[] data, int length)
Add data to the loader.

param
data Byte array of data.
param
length Number of objects in data.

private native intnativeCreateResponse(java.lang.String url, int statusCode, java.lang.String statusText, java.lang.String mimeType, long expectedLength, java.lang.String encoding, long expireTime)
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 voidnativeError(int id, java.lang.String desc, java.lang.String failingUrl)
Tell the loader there is error

param
id
param
desc
param
failingUrl The url that failed.

private native voidnativeFinished()
Tell the loader it has finished.

private native voidnativeReceivedResponse(int nativeResponse)
Dispatch the response.

param
nativeResponse The native pointer.

private native java.lang.StringnativeRedirectedToUrl(java.lang.String baseUrl, java.lang.String redirectTo, int nativeResponse)
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 voidnativeSetResponseHeader(int nativeResponse, java.lang.String key, java.lang.String val)
Add a response header to the native object.

param
nativeResponse The native pointer.
param
key String key.
param
val String value.

voidnotifyError()
Notify the loader we encountered an error.

        if (mNativeLoader != 0) {
            String description = getErrorDescription();
            if (description == null) description = "";
            nativeError(getErrorID(), description, url());
            clearNativeLoader();
        }
    
java.lang.StringoriginalUrl()

return
The original URL associated with this load.

        if (mOriginalUrl != null) {
            return mOriginalUrl;
        } else {
            return mUrl;
        }
    
private android.net.http.HttpAuthHeaderparseAuthHeader(java.lang.String header)

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.

        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;
    
private voidparseContentTypeHeader(java.lang.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();
            }
        }
    
booleanproxyAuthenticate()

return
True iff this loader is in the proxy-authenticate state.

        if (mAuthHeader != null) {
            return mAuthHeader.isProxy();
        }

        return false;
    
java.lang.Stringrealm()

return
HTTP authentication realm or null if none.

        if (mAuthHeader == null) {
            return null;
        } else {
            return mAuthHeader.getRealm();
        }
    
voidresetCancel()

        mCancelled = false;
    
private voidsendMessageInternal(android.os.Message msg)
Either send a message to ourselves or queue the message if this is a synchronous load.

        if (mSynchronous) {
            mMessageQueue.add(msg);
        } else {
            sendMessage(msg);
        }
    
voidsetCacheLoader(CacheLoader c)

        mCacheLoader = c;
    
voidsetRequestData(java.lang.String method, java.util.Map headers, byte[] postData, boolean isHighPriority)
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

        mMethod = method;
        mRequestHeaders = headers;
        mPostData = postData;
        mIsHighPriority = isHighPriority;
    
voidsetUrl(java.lang.String url)
Sets the current URL associated with this load.

        if (url != null) {
            mUri = null;
            if (URLUtil.isNetworkUrl(url)) {
                mUrl = URLUtil.stripAnchor(url);
                try {
                    mUri = new WebAddress(mUrl);
                } catch (ParseException e) {
                    e.printStackTrace();
                }
            } else {
                mUrl = url;
            }
        }
    
android.net.http.SslErrorsslError()

return
The last SSL error or null if there is none

        return mSslError;
    
public voidstatus(int majorVersion, int minorVersion, int code, java.lang.String reasonPhrase)
Report the status of the response. TODO: Comments about each parameter. IMPORTANT: as this is called from network thread, can't call native directly

        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);
        // New status means new data. Clear the old.
        mDataBuilder.clear();
        sendMessageInternal(obtainMessage(MSG_STATUS, status));
    
voidtearDown()
Tear down the load. Subclasses should clean up any mess because of cancellation or errors during the load.

        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();
        }
    
java.lang.Stringurl()

return
The current URL associated with this load.

        return mUrl;
    
static booleanwillLoadFromCache(java.lang.String url)

        boolean inCache = CacheManager.getCacheFile(url, null) != null;
        if (Config.LOGV) {
            Log.v(LOGTAG, "willLoadFromCache: " + url + " in cache: " + 
                    inCache);
        }
        return inCache;