1/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.webkit;
18
19import android.app.ActivityManager;
20import android.content.ComponentCallbacks;
21import android.content.Context;
22import android.content.res.AssetManager;
23import android.content.res.Configuration;
24import android.database.Cursor;
25import android.graphics.Bitmap;
26import android.net.ParseException;
27import android.net.Uri;
28import android.net.WebAddress;
29import android.net.http.SslCertificate;
30import android.os.Handler;
31import android.os.Message;
32import android.provider.OpenableColumns;
33import android.util.Log;
34import android.util.TypedValue;
35import android.view.Surface;
36import android.view.ViewRoot;
37import android.view.WindowManager;
38
39import junit.framework.Assert;
40
41import java.io.InputStream;
42import java.lang.ref.WeakReference;
43import java.net.URLEncoder;
44import java.util.ArrayList;
45import java.util.HashMap;
46import java.util.Map;
47import java.util.Iterator;
48
49class BrowserFrame extends Handler {
50
51    private static final String LOGTAG = "webkit";
52
53    /**
54     * Cap the number of LoadListeners that will be instantiated, so
55     * we don't blow the GREF count.  Attempting to queue more than
56     * this many requests will prompt an error() callback on the
57     * request's LoadListener
58     */
59    private final static int MAX_OUTSTANDING_REQUESTS = 300;
60
61    private final CallbackProxy mCallbackProxy;
62    private final WebSettings mSettings;
63    private final Context mContext;
64    private final WebViewDatabase mDatabase;
65    private final WebViewCore mWebViewCore;
66    /* package */ boolean mLoadInitFromJava;
67    private int mLoadType;
68    private boolean mFirstLayoutDone = true;
69    private boolean mCommitted = true;
70    // Flag for blocking messages. This is used during destroy() so
71    // that if the UI thread posts any messages after the message
72    // queue has been cleared,they are ignored.
73    private boolean mBlockMessages = false;
74    private int mOrientation = -1;
75
76    // Is this frame the main frame?
77    private boolean mIsMainFrame;
78
79    // Attached Javascript interfaces
80    private Map<String, Object> mJSInterfaceMap;
81
82    // message ids
83    // a message posted when a frame loading is completed
84    static final int FRAME_COMPLETED = 1001;
85    // orientation change message
86    static final int ORIENTATION_CHANGED = 1002;
87    // a message posted when the user decides the policy
88    static final int POLICY_FUNCTION = 1003;
89
90    // Note: need to keep these in sync with FrameLoaderTypes.h in native
91    static final int FRAME_LOADTYPE_STANDARD = 0;
92    static final int FRAME_LOADTYPE_BACK = 1;
93    static final int FRAME_LOADTYPE_FORWARD = 2;
94    static final int FRAME_LOADTYPE_INDEXEDBACKFORWARD = 3;
95    static final int FRAME_LOADTYPE_RELOAD = 4;
96    static final int FRAME_LOADTYPE_RELOADALLOWINGSTALEDATA = 5;
97    static final int FRAME_LOADTYPE_SAME = 6;
98    static final int FRAME_LOADTYPE_REDIRECT = 7;
99    static final int FRAME_LOADTYPE_REPLACE = 8;
100
101    // A progress threshold to switch from history Picture to live Picture
102    private static final int TRANSITION_SWITCH_THRESHOLD = 75;
103
104    // This is a field accessed by native code as well as package classes.
105    /*package*/ int mNativeFrame;
106
107    // Static instance of a JWebCoreJavaBridge to handle timer and cookie
108    // requests from WebCore.
109    static JWebCoreJavaBridge sJavaBridge;
110
111    private static class ConfigCallback implements ComponentCallbacks {
112        private final ArrayList<WeakReference<Handler>> mHandlers =
113                new ArrayList<WeakReference<Handler>>();
114        private final WindowManager mWindowManager;
115
116        ConfigCallback(WindowManager wm) {
117            mWindowManager = wm;
118        }
119
120        public synchronized void addHandler(Handler h) {
121            // No need to ever remove a Handler. If the BrowserFrame is
122            // destroyed, it will be collected and the WeakReference set to
123            // null. If it happens to still be around during a configuration
124            // change, the message will be ignored.
125            mHandlers.add(new WeakReference<Handler>(h));
126        }
127
128        public void onConfigurationChanged(Configuration newConfig) {
129            if (mHandlers.size() == 0) {
130                return;
131            }
132            int orientation =
133                    mWindowManager.getDefaultDisplay().getOrientation();
134            switch (orientation) {
135                case Surface.ROTATION_90:
136                    orientation = 90;
137                    break;
138                case Surface.ROTATION_180:
139                    orientation = 180;
140                    break;
141                case Surface.ROTATION_270:
142                    orientation = -90;
143                    break;
144                case Surface.ROTATION_0:
145                    orientation = 0;
146                    break;
147                default:
148                    break;
149            }
150            synchronized (this) {
151                // Create a list of handlers to remove. Go ahead and make it
152                // the same size to avoid resizing.
153                ArrayList<WeakReference> handlersToRemove =
154                        new ArrayList<WeakReference>(mHandlers.size());
155                for (WeakReference<Handler> wh : mHandlers) {
156                    Handler h = wh.get();
157                    if (h != null) {
158                        h.sendMessage(h.obtainMessage(ORIENTATION_CHANGED,
159                                    orientation, 0));
160                    } else {
161                        handlersToRemove.add(wh);
162                    }
163                }
164                // Now remove all the null references.
165                for (WeakReference weak : handlersToRemove) {
166                    mHandlers.remove(weak);
167                }
168            }
169        }
170
171        public void onLowMemory() {}
172    }
173    static ConfigCallback sConfigCallback;
174
175    /**
176     * Create a new BrowserFrame to be used in an application.
177     * @param context An application context to use when retrieving assets.
178     * @param w A WebViewCore used as the view for this frame.
179     * @param proxy A CallbackProxy for posting messages to the UI thread and
180     *              querying a client for information.
181     * @param settings A WebSettings object that holds all settings.
182     * XXX: Called by WebCore thread.
183     */
184    public BrowserFrame(Context context, WebViewCore w, CallbackProxy proxy,
185            WebSettings settings, Map<String, Object> javascriptInterfaces) {
186
187        Context appContext = context.getApplicationContext();
188
189        // Create a global JWebCoreJavaBridge to handle timers and
190        // cookies in the WebCore thread.
191        if (sJavaBridge == null) {
192            sJavaBridge = new JWebCoreJavaBridge();
193            // set WebCore native cache size
194            ActivityManager am = (ActivityManager) context
195                    .getSystemService(Context.ACTIVITY_SERVICE);
196            if (am.getMemoryClass() > 16) {
197                sJavaBridge.setCacheSize(8 * 1024 * 1024);
198            } else {
199                sJavaBridge.setCacheSize(4 * 1024 * 1024);
200            }
201            // initialize CacheManager
202            CacheManager.init(appContext);
203            // create CookieSyncManager with current Context
204            CookieSyncManager.createInstance(appContext);
205            // create PluginManager with current Context
206            PluginManager.getInstance(appContext);
207        }
208
209        if (sConfigCallback == null) {
210            sConfigCallback = new ConfigCallback(
211                    (WindowManager) context.getSystemService(
212                            Context.WINDOW_SERVICE));
213            ViewRoot.addConfigCallback(sConfigCallback);
214        }
215        sConfigCallback.addHandler(this);
216
217        mJSInterfaceMap = javascriptInterfaces;
218
219        mSettings = settings;
220        mContext = context;
221        mCallbackProxy = proxy;
222        mDatabase = WebViewDatabase.getInstance(appContext);
223        mWebViewCore = w;
224
225        AssetManager am = context.getAssets();
226        nativeCreateFrame(w, am, proxy.getBackForwardList());
227
228        if (DebugFlags.BROWSER_FRAME) {
229            Log.v(LOGTAG, "BrowserFrame constructor: this=" + this);
230        }
231    }
232
233    /**
234     * Load a url from the network or the filesystem into the main frame.
235     * Following the same behaviour as Safari, javascript: URLs are not passed
236     * to the main frame, instead they are evaluated immediately.
237     * @param url The url to load.
238     * @param extraHeaders The extra headers sent with this url. This should not
239     *            include the common headers like "user-agent". If it does, it
240     *            will be replaced by the intrinsic value of the WebView.
241     */
242    public void loadUrl(String url, Map<String, String> extraHeaders) {
243        mLoadInitFromJava = true;
244        if (URLUtil.isJavaScriptUrl(url)) {
245            // strip off the scheme and evaluate the string
246            stringByEvaluatingJavaScriptFromString(
247                    url.substring("javascript:".length()));
248        } else {
249            nativeLoadUrl(url, extraHeaders);
250        }
251        mLoadInitFromJava = false;
252    }
253
254    /**
255     * Load a url with "POST" method from the network into the main frame.
256     * @param url The url to load.
257     * @param data The data for POST request.
258     */
259    public void postUrl(String url, byte[] data) {
260        mLoadInitFromJava = true;
261        nativePostUrl(url, data);
262        mLoadInitFromJava = false;
263    }
264
265    /**
266     * Load the content as if it was loaded by the provided base URL. The
267     * historyUrl is used as the history entry for the load data.
268     *
269     * @param baseUrl Base URL used to resolve relative paths in the content
270     * @param data Content to render in the browser
271     * @param mimeType Mimetype of the data being passed in
272     * @param encoding Character set encoding of the provided data.
273     * @param historyUrl URL to use as the history entry.
274     */
275    public void loadData(String baseUrl, String data, String mimeType,
276            String encoding, String historyUrl) {
277        mLoadInitFromJava = true;
278        if (historyUrl == null || historyUrl.length() == 0) {
279            historyUrl = "about:blank";
280        }
281        if (data == null) {
282            data = "";
283        }
284
285        // Setup defaults for missing values. These defaults where taken from
286        // WebKit's WebFrame.mm
287        if (baseUrl == null || baseUrl.length() == 0) {
288            baseUrl = "about:blank";
289        }
290        if (mimeType == null || mimeType.length() == 0) {
291            mimeType = "text/html";
292        }
293        nativeLoadData(baseUrl, data, mimeType, encoding, historyUrl);
294        mLoadInitFromJava = false;
295    }
296
297    /**
298     * Go back or forward the number of steps given.
299     * @param steps A negative or positive number indicating the direction
300     *              and number of steps to move.
301     */
302    public void goBackOrForward(int steps) {
303        mLoadInitFromJava = true;
304        nativeGoBackOrForward(steps);
305        mLoadInitFromJava = false;
306    }
307
308    /**
309     * native callback
310     * Report an error to an activity.
311     * @param errorCode The HTTP error code.
312     * @param description A String description.
313     * TODO: Report all errors including resource errors but include some kind
314     * of domain identifier. Change errorCode to an enum for a cleaner
315     * interface.
316     */
317    private void reportError(final int errorCode, final String description,
318            final String failingUrl) {
319        // As this is called for the main resource and loading will be stopped
320        // after, reset the state variables.
321        resetLoadingStates();
322        mCallbackProxy.onReceivedError(errorCode, description, failingUrl);
323    }
324
325    private void resetLoadingStates() {
326        mCommitted = true;
327        mFirstLayoutDone = true;
328    }
329
330    /* package */boolean committed() {
331        return mCommitted;
332    }
333
334    /* package */boolean firstLayoutDone() {
335        return mFirstLayoutDone;
336    }
337
338    /* package */int loadType() {
339        return mLoadType;
340    }
341
342    /* package */void didFirstLayout() {
343        if (!mFirstLayoutDone) {
344            mFirstLayoutDone = true;
345            // ensure {@link WebViewCore#webkitDraw} is called as we were
346            // blocking the update in {@link #loadStarted}
347            mWebViewCore.contentDraw();
348        }
349    }
350
351    /**
352     * native callback
353     * Indicates the beginning of a new load.
354     * This method will be called once for the main frame.
355     */
356    private void loadStarted(String url, Bitmap favicon, int loadType,
357            boolean isMainFrame) {
358        mIsMainFrame = isMainFrame;
359
360        if (isMainFrame || loadType == FRAME_LOADTYPE_STANDARD) {
361            mLoadType = loadType;
362
363            if (isMainFrame) {
364                // Call onPageStarted for main frames.
365                mCallbackProxy.onPageStarted(url, favicon);
366                // as didFirstLayout() is only called for the main frame, reset
367                // mFirstLayoutDone only for the main frames
368                mFirstLayoutDone = false;
369                mCommitted = false;
370                // remove pending draw to block update until mFirstLayoutDone is
371                // set to true in didFirstLayout()
372                mWebViewCore.removeMessages(WebViewCore.EventHub.WEBKIT_DRAW);
373            }
374
375            // Note: only saves committed form data in standard load
376            if (loadType == FRAME_LOADTYPE_STANDARD
377                    && mSettings.getSaveFormData()) {
378                final WebHistoryItem h = mCallbackProxy.getBackForwardList()
379                        .getCurrentItem();
380                if (h != null) {
381                    String currentUrl = h.getUrl();
382                    if (currentUrl != null) {
383                        mDatabase.setFormData(currentUrl, getFormTextData());
384                    }
385                }
386            }
387        }
388    }
389
390    /**
391     * native callback
392     * Indicates the WebKit has committed to the new load
393     */
394    private void transitionToCommitted(int loadType, boolean isMainFrame) {
395        // loadType is not used yet
396        if (isMainFrame) {
397            mCommitted = true;
398            mWebViewCore.getWebView().mViewManager.postResetStateAll();
399        }
400    }
401
402    /**
403     * native callback
404     * <p>
405     * Indicates the end of a new load.
406     * This method will be called once for the main frame.
407     */
408    private void loadFinished(String url, int loadType, boolean isMainFrame) {
409        // mIsMainFrame and isMainFrame are better be equal!!!
410
411        if (isMainFrame || loadType == FRAME_LOADTYPE_STANDARD) {
412            if (isMainFrame) {
413                resetLoadingStates();
414                mCallbackProxy.switchOutDrawHistory();
415                mCallbackProxy.onPageFinished(url);
416            }
417        }
418    }
419
420    /**
421     * We have received an SSL certificate for the main top-level page.
422     *
423     * !!!Called from the network thread!!!
424     */
425    void certificate(SslCertificate certificate) {
426        if (mIsMainFrame) {
427            // we want to make this call even if the certificate is null
428            // (ie, the site is not secure)
429            mCallbackProxy.onReceivedCertificate(certificate);
430        }
431    }
432
433    /**
434     * Destroy all native components of the BrowserFrame.
435     */
436    public void destroy() {
437        nativeDestroyFrame();
438        mBlockMessages = true;
439        removeCallbacksAndMessages(null);
440    }
441
442    /**
443     * Handle messages posted to us.
444     * @param msg The message to handle.
445     */
446    @Override
447    public void handleMessage(Message msg) {
448        if (mBlockMessages) {
449            return;
450        }
451        switch (msg.what) {
452            case FRAME_COMPLETED: {
453                if (mSettings.getSavePassword() && hasPasswordField()) {
454                    WebHistoryItem item = mCallbackProxy.getBackForwardList()
455                            .getCurrentItem();
456                    if (item != null) {
457                        WebAddress uri = new WebAddress(item.getUrl());
458                        String schemePlusHost = uri.mScheme + uri.mHost;
459                        String[] up =
460                                mDatabase.getUsernamePassword(schemePlusHost);
461                        if (up != null && up[0] != null) {
462                            setUsernamePassword(up[0], up[1]);
463                        }
464                    }
465                }
466                WebViewWorker.getHandler().sendEmptyMessage(
467                        WebViewWorker.MSG_TRIM_CACHE);
468                break;
469            }
470
471            case POLICY_FUNCTION: {
472                nativeCallPolicyFunction(msg.arg1, msg.arg2);
473                break;
474            }
475
476            case ORIENTATION_CHANGED: {
477                if (mOrientation != msg.arg1) {
478                    mOrientation = msg.arg1;
479                    nativeOrientationChanged(msg.arg1);
480                }
481                break;
482            }
483
484            default:
485                break;
486        }
487    }
488
489    /**
490     * Punch-through for WebCore to set the document
491     * title. Inform the Activity of the new title.
492     * @param title The new title of the document.
493     */
494    private void setTitle(String title) {
495        // FIXME: The activity must call getTitle (a native method) to get the
496        // title. We should try and cache the title if we can also keep it in
497        // sync with the document.
498        mCallbackProxy.onReceivedTitle(title);
499    }
500
501    /**
502     * Retrieves the render tree of this frame and puts it as the object for
503     * the message and sends the message.
504     * @param callback the message to use to send the render tree
505     */
506    public void externalRepresentation(Message callback) {
507        callback.obj = externalRepresentation();;
508        callback.sendToTarget();
509    }
510
511    /**
512     * Return the render tree as a string
513     */
514    private native String externalRepresentation();
515
516    /**
517     * Retrieves the visual text of the current frame, puts it as the object for
518     * the message and sends the message.
519     * @param callback the message to use to send the visual text
520     */
521    public void documentAsText(Message callback) {
522        callback.obj = documentAsText();;
523        callback.sendToTarget();
524    }
525
526    /**
527     * Return the text drawn on the screen as a string
528     */
529    private native String documentAsText();
530
531    /*
532     * This method is called by WebCore to inform the frame that
533     * the Javascript window object has been cleared.
534     * We should re-attach any attached js interfaces.
535     */
536    private void windowObjectCleared(int nativeFramePointer) {
537        if (mJSInterfaceMap != null) {
538            Iterator iter = mJSInterfaceMap.keySet().iterator();
539            while (iter.hasNext())  {
540                String interfaceName = (String) iter.next();
541                nativeAddJavascriptInterface(nativeFramePointer,
542                        mJSInterfaceMap.get(interfaceName), interfaceName);
543            }
544        }
545    }
546
547    /**
548     * This method is called by WebCore to check whether application
549     * wants to hijack url loading
550     */
551    public boolean handleUrl(String url) {
552        if (mLoadInitFromJava == true) {
553            return false;
554        }
555        if (mCallbackProxy.shouldOverrideUrlLoading(url)) {
556            // if the url is hijacked, reset the state of the BrowserFrame
557            didFirstLayout();
558            return true;
559        } else {
560            return false;
561        }
562    }
563
564    public void addJavascriptInterface(Object obj, String interfaceName) {
565        if (mJSInterfaceMap == null) {
566            mJSInterfaceMap = new HashMap<String, Object>();
567        }
568        if (mJSInterfaceMap.containsKey(interfaceName)) {
569            mJSInterfaceMap.remove(interfaceName);
570        }
571        mJSInterfaceMap.put(interfaceName, obj);
572    }
573
574    /**
575     * Called by JNI.  Given a URI, find the associated file and return its size
576     * @param uri A String representing the URI of the desired file.
577     * @return int The size of the given file.
578     */
579    private int getFileSize(String uri) {
580        int size = 0;
581        try {
582            InputStream stream = mContext.getContentResolver()
583                            .openInputStream(Uri.parse(uri));
584            size = stream.available();
585            stream.close();
586        } catch (Exception e) {}
587        return size;
588    }
589
590    /**
591     * Called by JNI.  Given a URI, a buffer, and an offset into the buffer,
592     * copy the resource into buffer.
593     * @param uri A String representing the URI of the desired file.
594     * @param buffer The byte array to copy the data into.
595     * @param offset The offet into buffer to place the data.
596     * @param expectedSize The size that the buffer has allocated for this file.
597     * @return int The size of the given file, or zero if it fails.
598     */
599    private int getFile(String uri, byte[] buffer, int offset,
600            int expectedSize) {
601        int size = 0;
602        try {
603            InputStream stream = mContext.getContentResolver()
604                            .openInputStream(Uri.parse(uri));
605            size = stream.available();
606            if (size <= expectedSize && buffer != null
607                    && buffer.length - offset >= size) {
608                stream.read(buffer, offset, size);
609            } else {
610                size = 0;
611            }
612            stream.close();
613        } catch (java.io.FileNotFoundException e) {
614            Log.e(LOGTAG, "FileNotFoundException:" + e);
615            size = 0;
616        } catch (java.io.IOException e2) {
617            Log.e(LOGTAG, "IOException: " + e2);
618            size = 0;
619        }
620        return size;
621    }
622
623    /**
624     * Start loading a resource.
625     * @param loaderHandle The native ResourceLoader that is the target of the
626     *                     data.
627     * @param url The url to load.
628     * @param method The http method.
629     * @param headers The http headers.
630     * @param postData If the method is "POST" postData is sent as the request
631     *                 body. Is null when empty.
632     * @param postDataIdentifier If the post data contained form this is the form identifier, otherwise it is 0.
633     * @param cacheMode The cache mode to use when loading this resource. See WebSettings.setCacheMode
634     * @param mainResource True if the this resource is the main request, not a supporting resource
635     * @param userGesture
636     * @param synchronous True if the load is synchronous.
637     * @return A newly created LoadListener object.
638     */
639    private LoadListener startLoadingResource(int loaderHandle,
640                                              String url,
641                                              String method,
642                                              HashMap headers,
643                                              byte[] postData,
644                                              long postDataIdentifier,
645                                              int cacheMode,
646                                              boolean mainResource,
647                                              boolean userGesture,
648                                              boolean synchronous,
649                                              String username,
650                                              String password) {
651        PerfChecker checker = new PerfChecker();
652
653        if (mSettings.getCacheMode() != WebSettings.LOAD_DEFAULT) {
654            cacheMode = mSettings.getCacheMode();
655        }
656
657        if (method.equals("POST")) {
658            // Don't use the cache on POSTs when issuing a normal POST
659            // request.
660            if (cacheMode == WebSettings.LOAD_NORMAL) {
661                cacheMode = WebSettings.LOAD_NO_CACHE;
662            }
663            if (mSettings.getSavePassword() && hasPasswordField()) {
664                try {
665                    if (DebugFlags.BROWSER_FRAME) {
666                        Assert.assertNotNull(mCallbackProxy.getBackForwardList()
667                                .getCurrentItem());
668                    }
669                    WebAddress uri = new WebAddress(mCallbackProxy
670                            .getBackForwardList().getCurrentItem().getUrl());
671                    String schemePlusHost = uri.mScheme + uri.mHost;
672                    String[] ret = getUsernamePassword();
673                    // Has the user entered a username/password pair and is
674                    // there some POST data
675                    if (ret != null && postData != null &&
676                            ret[0].length() > 0 && ret[1].length() > 0) {
677                        // Check to see if the username & password appear in
678                        // the post data (there could be another form on the
679                        // page and that was posted instead.
680                        String postString = new String(postData);
681                        if (postString.contains(URLEncoder.encode(ret[0])) &&
682                                postString.contains(URLEncoder.encode(ret[1]))) {
683                            String[] saved = mDatabase.getUsernamePassword(
684                                    schemePlusHost);
685                            if (saved != null) {
686                                // null username implies that user has chosen not to
687                                // save password
688                                if (saved[0] != null) {
689                                    // non-null username implies that user has
690                                    // chosen to save password, so update the
691                                    // recorded password
692                                    mDatabase.setUsernamePassword(
693                                            schemePlusHost, ret[0], ret[1]);
694                                }
695                            } else {
696                                // CallbackProxy will handle creating the resume
697                                // message
698                                mCallbackProxy.onSavePassword(schemePlusHost, ret[0],
699                                        ret[1], null);
700                            }
701                        }
702                    }
703                } catch (ParseException ex) {
704                    // if it is bad uri, don't save its password
705                }
706
707            }
708        }
709
710        // is this resource the main-frame top-level page?
711        boolean isMainFramePage = mIsMainFrame;
712
713        if (DebugFlags.BROWSER_FRAME) {
714            Log.v(LOGTAG, "startLoadingResource: url=" + url + ", method="
715                    + method + ", postData=" + postData + ", isMainFramePage="
716                    + isMainFramePage + ", mainResource=" + mainResource
717                    + ", userGesture=" + userGesture);
718        }
719
720        // Create a LoadListener
721        LoadListener loadListener = LoadListener.getLoadListener(mContext,
722                this, url, loaderHandle, synchronous, isMainFramePage,
723                mainResource, userGesture, postDataIdentifier, username, password);
724
725        mCallbackProxy.onLoadResource(url);
726
727        if (LoadListener.getNativeLoaderCount() > MAX_OUTSTANDING_REQUESTS) {
728            // send an error message, so that loadListener can be deleted
729            // after this is returned. This is important as LoadListener's
730            // nativeError will remove the request from its DocLoader's request
731            // list. But the set up is not done until this method is returned.
732            loadListener.error(
733                    android.net.http.EventHandler.ERROR, mContext.getString(
734                            com.android.internal.R.string.httpErrorTooManyRequests));
735            return loadListener;
736        }
737
738        FrameLoader loader = new FrameLoader(loadListener, mSettings, method);
739        loader.setHeaders(headers);
740        loader.setPostData(postData);
741        // Set the load mode to the mode used for the current page.
742        // If WebKit wants validation, go to network directly.
743        loader.setCacheMode(headers.containsKey("If-Modified-Since")
744                || headers.containsKey("If-None-Match") ?
745                        WebSettings.LOAD_NO_CACHE : cacheMode);
746        // Set referrer to current URL?
747        if (!loader.executeLoad()) {
748            checker.responseAlert("startLoadingResource fail");
749        }
750        checker.responseAlert("startLoadingResource succeed");
751
752        return !synchronous ? loadListener : null;
753    }
754
755    /**
756     * Set the progress for the browser activity.  Called by native code.
757     * Uses a delay so it does not happen too often.
758     * @param newProgress An int between zero and one hundred representing
759     *                    the current progress percentage of loading the page.
760     */
761    private void setProgress(int newProgress) {
762        mCallbackProxy.onProgressChanged(newProgress);
763        if (newProgress == 100) {
764            sendMessageDelayed(obtainMessage(FRAME_COMPLETED), 100);
765        }
766        // FIXME: Need to figure out a better way to switch out of the history
767        // drawing mode. Maybe we can somehow compare the history picture with
768        // the current picture, and switch when it contains more content.
769        if (mFirstLayoutDone && newProgress > TRANSITION_SWITCH_THRESHOLD) {
770            mCallbackProxy.switchOutDrawHistory();
771        }
772    }
773
774    /**
775     * Send the icon to the activity for display.
776     * @param icon A Bitmap representing a page's favicon.
777     */
778    private void didReceiveIcon(Bitmap icon) {
779        mCallbackProxy.onReceivedIcon(icon);
780    }
781
782    // Called by JNI when an apple-touch-icon attribute was found.
783    private void didReceiveTouchIconUrl(String url, boolean precomposed) {
784        mCallbackProxy.onReceivedTouchIconUrl(url, precomposed);
785    }
786
787    /**
788     * Request a new window from the client.
789     * @return The BrowserFrame object stored in the new WebView.
790     */
791    private BrowserFrame createWindow(boolean dialog, boolean userGesture) {
792        return mCallbackProxy.createWindow(dialog, userGesture);
793    }
794
795    /**
796     * Try to focus this WebView.
797     */
798    private void requestFocus() {
799        mCallbackProxy.onRequestFocus();
800    }
801
802    /**
803     * Close this frame and window.
804     */
805    private void closeWindow(WebViewCore w) {
806        mCallbackProxy.onCloseWindow(w.getWebView());
807    }
808
809    // XXX: Must match PolicyAction in FrameLoaderTypes.h in webcore
810    static final int POLICY_USE = 0;
811    static final int POLICY_IGNORE = 2;
812
813    private void decidePolicyForFormResubmission(int policyFunction) {
814        Message dontResend = obtainMessage(POLICY_FUNCTION, policyFunction,
815                POLICY_IGNORE);
816        Message resend = obtainMessage(POLICY_FUNCTION, policyFunction,
817                POLICY_USE);
818        mCallbackProxy.onFormResubmission(dontResend, resend);
819    }
820
821    /**
822     * Tell the activity to update its global history.
823     */
824    private void updateVisitedHistory(String url, boolean isReload) {
825        mCallbackProxy.doUpdateVisitedHistory(url, isReload);
826    }
827
828    /**
829     * Get the CallbackProxy for sending messages to the UI thread.
830     */
831    /* package */ CallbackProxy getCallbackProxy() {
832        return mCallbackProxy;
833    }
834
835    /**
836     * Returns the User Agent used by this frame
837     */
838    String getUserAgentString() {
839        return mSettings.getUserAgentString();
840    }
841
842    // These ids need to be in sync with enum rawResId in PlatformBridge.h
843    private static final int NODOMAIN = 1;
844    private static final int LOADERROR = 2;
845    private static final int DRAWABLEDIR = 3;
846    private static final int FILE_UPLOAD_LABEL = 4;
847    private static final int RESET_LABEL = 5;
848    private static final int SUBMIT_LABEL = 6;
849
850    String getRawResFilename(int id) {
851        int resid;
852        switch (id) {
853            case NODOMAIN:
854                resid = com.android.internal.R.raw.nodomain;
855                break;
856
857            case LOADERROR:
858                resid = com.android.internal.R.raw.loaderror;
859                break;
860
861            case DRAWABLEDIR:
862                // use one known resource to find the drawable directory
863                resid = com.android.internal.R.drawable.btn_check_off;
864                break;
865
866            case FILE_UPLOAD_LABEL:
867                return mContext.getResources().getString(
868                        com.android.internal.R.string.upload_file);
869
870            case RESET_LABEL:
871                return mContext.getResources().getString(
872                        com.android.internal.R.string.reset);
873
874            case SUBMIT_LABEL:
875                return mContext.getResources().getString(
876                        com.android.internal.R.string.submit);
877
878            default:
879                Log.e(LOGTAG, "getRawResFilename got incompatible resource ID");
880                return "";
881        }
882        TypedValue value = new TypedValue();
883        mContext.getResources().getValue(resid, value, true);
884        if (id == DRAWABLEDIR) {
885            String path = value.string.toString();
886            int index = path.lastIndexOf('/');
887            if (index < 0) {
888                Log.e(LOGTAG, "Can't find drawable directory.");
889                return "";
890            }
891            return path.substring(0, index + 1);
892        }
893        return value.string.toString();
894    }
895
896    private float density() {
897        return mContext.getResources().getDisplayMetrics().density;
898    }
899
900    //==========================================================================
901    // native functions
902    //==========================================================================
903
904    /**
905     * Create a new native frame for a given WebView
906     * @param w     A WebView that the frame draws into.
907     * @param am    AssetManager to use to get assets.
908     * @param list  The native side will add and remove items from this list as
909     *              the native list changes.
910     */
911    private native void nativeCreateFrame(WebViewCore w, AssetManager am,
912            WebBackForwardList list);
913
914    /**
915     * Destroy the native frame.
916     */
917    public native void nativeDestroyFrame();
918
919    private native void nativeCallPolicyFunction(int policyFunction,
920            int decision);
921
922    /**
923     * Reload the current main frame.
924     */
925    public native void reload(boolean allowStale);
926
927    /**
928     * Go back or forward the number of steps given.
929     * @param steps A negative or positive number indicating the direction
930     *              and number of steps to move.
931     */
932    private native void nativeGoBackOrForward(int steps);
933
934    /**
935     * stringByEvaluatingJavaScriptFromString will execute the
936     * JS passed in in the context of this browser frame.
937     * @param script A javascript string to execute
938     *
939     * @return string result of execution or null
940     */
941    public native String stringByEvaluatingJavaScriptFromString(String script);
942
943    /**
944     * Add a javascript interface to the main frame.
945     */
946    private native void nativeAddJavascriptInterface(int nativeFramePointer,
947            Object obj, String interfaceName);
948
949    /**
950     * Enable or disable the native cache.
951     */
952    /* FIXME: The native cache is always on for now until we have a better
953     * solution for our 2 caches. */
954    private native void setCacheDisabled(boolean disabled);
955
956    public native boolean cacheDisabled();
957
958    public native void clearCache();
959
960    /**
961     * Returns false if the url is bad.
962     */
963    private native void nativeLoadUrl(String url, Map<String, String> headers);
964
965    private native void nativePostUrl(String url, byte[] postData);
966
967    private native void nativeLoadData(String baseUrl, String data,
968            String mimeType, String encoding, String historyUrl);
969
970    /**
971     * Stop loading the current page.
972     */
973    public void stopLoading() {
974        if (mIsMainFrame) {
975            resetLoadingStates();
976        }
977        nativeStopLoading();
978    }
979
980    private native void nativeStopLoading();
981
982    /**
983     * Return true if the document has images.
984     */
985    public native boolean documentHasImages();
986
987    /**
988     * @return TRUE if there is a password field in the current frame
989     */
990    private native boolean hasPasswordField();
991
992    /**
993     * Get username and password in the current frame. If found, String[0] is
994     * username and String[1] is password. Otherwise return NULL.
995     * @return String[]
996     */
997    private native String[] getUsernamePassword();
998
999    /**
1000     * Set username and password to the proper fields in the current frame
1001     * @param username
1002     * @param password
1003     */
1004    private native void setUsernamePassword(String username, String password);
1005
1006    /**
1007     * Get form's "text" type data associated with the current frame.
1008     * @return HashMap If succeed, returns a list of name/value pair. Otherwise
1009     *         returns null.
1010     */
1011    private native HashMap getFormTextData();
1012
1013    private native void nativeOrientationChanged(int orientation);
1014}
1015