BrowserFrame.java revision 658dcd87db14935275d5cbabdd63662c41062ed2
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.content.res.Resources;
25import android.content.res.Resources.NotFoundException;
26import android.graphics.Bitmap;
27import android.net.ParseException;
28import android.net.Uri;
29import android.net.WebAddress;
30import android.net.http.ErrorStrings;
31import android.net.http.SslCertificate;
32import android.net.http.SslError;
33import android.os.Handler;
34import android.os.Message;
35import android.util.Log;
36import android.util.TypedValue;
37import android.view.Surface;
38import android.view.ViewRootImpl;
39import android.view.WindowManager;
40
41import junit.framework.Assert;
42
43import java.io.IOException;
44import java.io.InputStream;
45import java.lang.ref.WeakReference;
46import java.net.URLEncoder;
47import java.nio.charset.Charsets;
48import java.security.PrivateKey;
49import java.security.cert.CertificateEncodingException;
50import java.security.cert.X509Certificate;
51import java.util.ArrayList;
52import java.util.HashMap;
53import java.util.HashSet;
54import java.util.Iterator;
55import java.util.Map;
56import java.util.Set;
57
58import org.apache.harmony.security.provider.cert.X509CertImpl;
59
60class BrowserFrame extends Handler {
61
62    private static final String LOGTAG = "webkit";
63
64    /**
65     * Cap the number of LoadListeners that will be instantiated, so
66     * we don't blow the GREF count.  Attempting to queue more than
67     * this many requests will prompt an error() callback on the
68     * request's LoadListener
69     */
70    private final static int MAX_OUTSTANDING_REQUESTS = 300;
71
72    private final CallbackProxy mCallbackProxy;
73    private final WebSettingsClassic mSettings;
74    private final Context mContext;
75    private final WebViewDatabase mDatabase;
76    private final WebViewCore mWebViewCore;
77    /* package */ boolean mLoadInitFromJava;
78    private int mLoadType;
79    private boolean mFirstLayoutDone = true;
80    private boolean mCommitted = true;
81    // Flag for blocking messages. This is used during destroy() so
82    // that if the UI thread posts any messages after the message
83    // queue has been cleared,they are ignored.
84    private boolean mBlockMessages = false;
85    private int mOrientation = -1;
86
87    // Is this frame the main frame?
88    private boolean mIsMainFrame;
89
90    // Attached Javascript interfaces
91    private Map<String, Object> mJavaScriptObjects;
92    private Set<Object> mRemovedJavaScriptObjects;
93
94    // Key store handler when Chromium HTTP stack is used.
95    private KeyStoreHandler mKeyStoreHandler = null;
96
97    // Implementation of the searchbox API.
98    private final SearchBoxImpl mSearchBox;
99
100    // message ids
101    // a message posted when a frame loading is completed
102    static final int FRAME_COMPLETED = 1001;
103    // orientation change message
104    static final int ORIENTATION_CHANGED = 1002;
105    // a message posted when the user decides the policy
106    static final int POLICY_FUNCTION = 1003;
107
108    // Note: need to keep these in sync with FrameLoaderTypes.h in native
109    static final int FRAME_LOADTYPE_STANDARD = 0;
110    static final int FRAME_LOADTYPE_BACK = 1;
111    static final int FRAME_LOADTYPE_FORWARD = 2;
112    static final int FRAME_LOADTYPE_INDEXEDBACKFORWARD = 3;
113    static final int FRAME_LOADTYPE_RELOAD = 4;
114    static final int FRAME_LOADTYPE_RELOADALLOWINGSTALEDATA = 5;
115    static final int FRAME_LOADTYPE_SAME = 6;
116    static final int FRAME_LOADTYPE_REDIRECT = 7;
117    static final int FRAME_LOADTYPE_REPLACE = 8;
118
119    // A progress threshold to switch from history Picture to live Picture
120    private static final int TRANSITION_SWITCH_THRESHOLD = 75;
121
122    // This is a field accessed by native code as well as package classes.
123    /*package*/ int mNativeFrame;
124
125    // Static instance of a JWebCoreJavaBridge to handle timer and cookie
126    // requests from WebCore.
127    static JWebCoreJavaBridge sJavaBridge;
128
129    private static class ConfigCallback implements ComponentCallbacks {
130        private final ArrayList<WeakReference<Handler>> mHandlers =
131                new ArrayList<WeakReference<Handler>>();
132        private final WindowManager mWindowManager;
133
134        ConfigCallback(WindowManager wm) {
135            mWindowManager = wm;
136        }
137
138        public synchronized void addHandler(Handler h) {
139            // No need to ever remove a Handler. If the BrowserFrame is
140            // destroyed, it will be collected and the WeakReference set to
141            // null. If it happens to still be around during a configuration
142            // change, the message will be ignored.
143            mHandlers.add(new WeakReference<Handler>(h));
144        }
145
146        public void onConfigurationChanged(Configuration newConfig) {
147            if (mHandlers.size() == 0) {
148                return;
149            }
150            int orientation =
151                    mWindowManager.getDefaultDisplay().getOrientation();
152            switch (orientation) {
153                case Surface.ROTATION_90:
154                    orientation = 90;
155                    break;
156                case Surface.ROTATION_180:
157                    orientation = 180;
158                    break;
159                case Surface.ROTATION_270:
160                    orientation = -90;
161                    break;
162                case Surface.ROTATION_0:
163                    orientation = 0;
164                    break;
165                default:
166                    break;
167            }
168            synchronized (this) {
169                // Create a list of handlers to remove. Go ahead and make it
170                // the same size to avoid resizing.
171                ArrayList<WeakReference> handlersToRemove =
172                        new ArrayList<WeakReference>(mHandlers.size());
173                for (WeakReference<Handler> wh : mHandlers) {
174                    Handler h = wh.get();
175                    if (h != null) {
176                        h.sendMessage(h.obtainMessage(ORIENTATION_CHANGED,
177                                    orientation, 0));
178                    } else {
179                        handlersToRemove.add(wh);
180                    }
181                }
182                // Now remove all the null references.
183                for (WeakReference weak : handlersToRemove) {
184                    mHandlers.remove(weak);
185                }
186            }
187        }
188
189        public void onLowMemory() {}
190    }
191    static ConfigCallback sConfigCallback;
192
193    /**
194     * Create a new BrowserFrame to be used in an application.
195     * @param context An application context to use when retrieving assets.
196     * @param w A WebViewCore used as the view for this frame.
197     * @param proxy A CallbackProxy for posting messages to the UI thread and
198     *              querying a client for information.
199     * @param settings A WebSettings object that holds all settings.
200     * XXX: Called by WebCore thread.
201     */
202    public BrowserFrame(Context context, WebViewCore w, CallbackProxy proxy,
203            WebSettingsClassic settings, Map<String, Object> javascriptInterfaces) {
204
205        Context appContext = context.getApplicationContext();
206
207        // Create a global JWebCoreJavaBridge to handle timers and
208        // cookies in the WebCore thread.
209        if (sJavaBridge == null) {
210            sJavaBridge = new JWebCoreJavaBridge();
211            // set WebCore native cache size
212            ActivityManager am = (ActivityManager) context
213                    .getSystemService(Context.ACTIVITY_SERVICE);
214            if (am.getMemoryClass() > 16) {
215                sJavaBridge.setCacheSize(8 * 1024 * 1024);
216            } else {
217                sJavaBridge.setCacheSize(4 * 1024 * 1024);
218            }
219            // initialize CacheManager
220            CacheManager.init(appContext);
221            // create CookieSyncManager with current Context
222            CookieSyncManager.createInstance(appContext);
223            // create PluginManager with current Context
224            PluginManager.getInstance(appContext);
225        }
226
227        if (sConfigCallback == null) {
228            sConfigCallback = new ConfigCallback(
229                    (WindowManager) appContext.getSystemService(
230                            Context.WINDOW_SERVICE));
231            ViewRootImpl.addConfigCallback(sConfigCallback);
232        }
233        sConfigCallback.addHandler(this);
234
235        mJavaScriptObjects = javascriptInterfaces;
236        if (mJavaScriptObjects == null) {
237            mJavaScriptObjects = new HashMap<String, Object>();
238        }
239        mRemovedJavaScriptObjects = new HashSet<Object>();
240
241        mSettings = settings;
242        mContext = context;
243        mCallbackProxy = proxy;
244        mDatabase = WebViewDatabase.getInstance(appContext);
245        mWebViewCore = w;
246
247        mSearchBox = new SearchBoxImpl(mWebViewCore, mCallbackProxy);
248        mJavaScriptObjects.put(SearchBoxImpl.JS_INTERFACE_NAME, mSearchBox);
249
250        AssetManager am = context.getAssets();
251        nativeCreateFrame(w, am, proxy.getBackForwardList());
252
253        if (DebugFlags.BROWSER_FRAME) {
254            Log.v(LOGTAG, "BrowserFrame constructor: this=" + this);
255        }
256    }
257
258    /**
259     * Load a url from the network or the filesystem into the main frame.
260     * Following the same behaviour as Safari, javascript: URLs are not passed
261     * to the main frame, instead they are evaluated immediately.
262     * @param url The url to load.
263     * @param extraHeaders The extra headers sent with this url. This should not
264     *            include the common headers like "user-agent". If it does, it
265     *            will be replaced by the intrinsic value of the WebView.
266     */
267    public void loadUrl(String url, Map<String, String> extraHeaders) {
268        mLoadInitFromJava = true;
269        if (URLUtil.isJavaScriptUrl(url)) {
270            // strip off the scheme and evaluate the string
271            stringByEvaluatingJavaScriptFromString(
272                    url.substring("javascript:".length()));
273        } else {
274            nativeLoadUrl(url, extraHeaders);
275        }
276        mLoadInitFromJava = false;
277    }
278
279    /**
280     * Load a url with "POST" method from the network into the main frame.
281     * @param url The url to load.
282     * @param data The data for POST request.
283     */
284    public void postUrl(String url, byte[] data) {
285        mLoadInitFromJava = true;
286        nativePostUrl(url, data);
287        mLoadInitFromJava = false;
288    }
289
290    /**
291     * Load the content as if it was loaded by the provided base URL. The
292     * historyUrl is used as the history entry for the load data.
293     *
294     * @param baseUrl Base URL used to resolve relative paths in the content
295     * @param data Content to render in the browser
296     * @param mimeType Mimetype of the data being passed in
297     * @param encoding Character set encoding of the provided data.
298     * @param historyUrl URL to use as the history entry.
299     */
300    public void loadData(String baseUrl, String data, String mimeType,
301            String encoding, String historyUrl) {
302        mLoadInitFromJava = true;
303        if (historyUrl == null || historyUrl.length() == 0) {
304            historyUrl = "about:blank";
305        }
306        if (data == null) {
307            data = "";
308        }
309
310        // Setup defaults for missing values. These defaults where taken from
311        // WebKit's WebFrame.mm
312        if (baseUrl == null || baseUrl.length() == 0) {
313            baseUrl = "about:blank";
314        }
315        if (mimeType == null || mimeType.length() == 0) {
316            mimeType = "text/html";
317        }
318        nativeLoadData(baseUrl, data, mimeType, encoding, historyUrl);
319        mLoadInitFromJava = false;
320    }
321
322    /**
323     * Saves the contents of the frame as a web archive.
324     *
325     * @param basename The filename where the archive should be placed.
326     * @param autoname If false, takes filename to be a file. If true, filename
327     *                 is assumed to be a directory in which a filename will be
328     *                 chosen according to the url of the current page.
329     */
330    /* package */ String saveWebArchive(String basename, boolean autoname) {
331        return nativeSaveWebArchive(basename, autoname);
332    }
333
334    /**
335     * Go back or forward the number of steps given.
336     * @param steps A negative or positive number indicating the direction
337     *              and number of steps to move.
338     */
339    public void goBackOrForward(int steps) {
340        mLoadInitFromJava = true;
341        nativeGoBackOrForward(steps);
342        mLoadInitFromJava = false;
343    }
344
345    /**
346     * native callback
347     * Report an error to an activity.
348     * @param errorCode The HTTP error code.
349     * @param description Optional human-readable description. If no description
350     *     is given, we'll use a standard localized error message.
351     * @param failingUrl The URL that was being loaded when the error occurred.
352     * TODO: Report all errors including resource errors but include some kind
353     * of domain identifier. Change errorCode to an enum for a cleaner
354     * interface.
355     */
356    private void reportError(int errorCode, String description, String failingUrl) {
357        // As this is called for the main resource and loading will be stopped
358        // after, reset the state variables.
359        resetLoadingStates();
360        if (description == null || description.isEmpty()) {
361            description = ErrorStrings.getString(errorCode, mContext);
362        }
363        mCallbackProxy.onReceivedError(errorCode, description, failingUrl);
364    }
365
366    private void resetLoadingStates() {
367        mCommitted = true;
368        mFirstLayoutDone = true;
369    }
370
371    /* package */boolean committed() {
372        return mCommitted;
373    }
374
375    /* package */boolean firstLayoutDone() {
376        return mFirstLayoutDone;
377    }
378
379    /* package */int loadType() {
380        return mLoadType;
381    }
382
383    /* package */void didFirstLayout() {
384        if (!mFirstLayoutDone) {
385            mFirstLayoutDone = true;
386            // ensure {@link WebViewCore#webkitDraw} is called as we were
387            // blocking the update in {@link #loadStarted}
388            mWebViewCore.contentDraw();
389        }
390    }
391
392    /**
393     * native callback
394     * Indicates the beginning of a new load.
395     * This method will be called once for the main frame.
396     */
397    private void loadStarted(String url, Bitmap favicon, int loadType,
398            boolean isMainFrame) {
399        mIsMainFrame = isMainFrame;
400
401        if (isMainFrame || loadType == FRAME_LOADTYPE_STANDARD) {
402            mLoadType = loadType;
403
404            if (isMainFrame) {
405                // Call onPageStarted for main frames.
406                mCallbackProxy.onPageStarted(url, favicon);
407                // as didFirstLayout() is only called for the main frame, reset
408                // mFirstLayoutDone only for the main frames
409                mFirstLayoutDone = false;
410                mCommitted = false;
411                // remove pending draw to block update until mFirstLayoutDone is
412                // set to true in didFirstLayout()
413                mWebViewCore.clearContent();
414                mWebViewCore.removeMessages(WebViewCore.EventHub.WEBKIT_DRAW);
415            }
416        }
417    }
418
419    @SuppressWarnings("unused")
420    private void saveFormData(HashMap<String, String> data) {
421        if (mSettings.getSaveFormData()) {
422            final WebHistoryItem h = mCallbackProxy.getBackForwardList()
423                    .getCurrentItem();
424            if (h != null) {
425                String url = WebTextView.urlForAutoCompleteData(h.getUrl());
426                if (url != null) {
427                    mDatabase.setFormData(url, data);
428                }
429            }
430        }
431    }
432
433    @SuppressWarnings("unused")
434    private boolean shouldSaveFormData() {
435        if (mSettings.getSaveFormData()) {
436            final WebHistoryItem h = mCallbackProxy.getBackForwardList()
437                    .getCurrentItem();
438            return h != null && h.getUrl() != null;
439        }
440        return false;
441    }
442
443    /**
444     * native callback
445     * Indicates the WebKit has committed to the new load
446     */
447    private void transitionToCommitted(int loadType, boolean isMainFrame) {
448        // loadType is not used yet
449        if (isMainFrame) {
450            mCommitted = true;
451            mWebViewCore.getWebView().mViewManager.postResetStateAll();
452        }
453    }
454
455    /**
456     * native callback
457     * <p>
458     * Indicates the end of a new load.
459     * This method will be called once for the main frame.
460     */
461    private void loadFinished(String url, int loadType, boolean isMainFrame) {
462        // mIsMainFrame and isMainFrame are better be equal!!!
463
464        if (isMainFrame || loadType == FRAME_LOADTYPE_STANDARD) {
465            if (isMainFrame) {
466                resetLoadingStates();
467                mCallbackProxy.switchOutDrawHistory();
468                mCallbackProxy.onPageFinished(url);
469            }
470        }
471    }
472
473    /**
474     * Destroy all native components of the BrowserFrame.
475     */
476    public void destroy() {
477        nativeDestroyFrame();
478        mBlockMessages = true;
479        removeCallbacksAndMessages(null);
480    }
481
482    /**
483     * Handle messages posted to us.
484     * @param msg The message to handle.
485     */
486    @Override
487    public void handleMessage(Message msg) {
488        if (mBlockMessages) {
489            return;
490        }
491        switch (msg.what) {
492            case FRAME_COMPLETED: {
493                if (mSettings.getSavePassword() && hasPasswordField()) {
494                    WebHistoryItem item = mCallbackProxy.getBackForwardList()
495                            .getCurrentItem();
496                    if (item != null) {
497                        WebAddress uri = new WebAddress(item.getUrl());
498                        String schemePlusHost = uri.getScheme() + uri.getHost();
499                        String[] up =
500                                mDatabase.getUsernamePassword(schemePlusHost);
501                        if (up != null && up[0] != null) {
502                            setUsernamePassword(up[0], up[1]);
503                        }
504                    }
505                }
506                break;
507            }
508
509            case POLICY_FUNCTION: {
510                nativeCallPolicyFunction(msg.arg1, msg.arg2);
511                break;
512            }
513
514            case ORIENTATION_CHANGED: {
515                if (mOrientation != msg.arg1) {
516                    mOrientation = msg.arg1;
517                    nativeOrientationChanged(msg.arg1);
518                }
519                break;
520            }
521
522            default:
523                break;
524        }
525    }
526
527    /**
528     * Punch-through for WebCore to set the document
529     * title. Inform the Activity of the new title.
530     * @param title The new title of the document.
531     */
532    private void setTitle(String title) {
533        // FIXME: The activity must call getTitle (a native method) to get the
534        // title. We should try and cache the title if we can also keep it in
535        // sync with the document.
536        mCallbackProxy.onReceivedTitle(title);
537    }
538
539    /**
540     * Retrieves the render tree of this frame and puts it as the object for
541     * the message and sends the message.
542     * @param callback the message to use to send the render tree
543     */
544    public void externalRepresentation(Message callback) {
545        callback.obj = externalRepresentation();;
546        callback.sendToTarget();
547    }
548
549    /**
550     * Return the render tree as a string
551     */
552    private native String externalRepresentation();
553
554    /**
555     * Retrieves the visual text of the frames, puts it as the object for
556     * the message and sends the message.
557     * @param callback the message to use to send the visual text
558     */
559    public void documentAsText(Message callback) {
560        StringBuilder text = new StringBuilder();
561        if (callback.arg1 != 0) {
562            // Dump top frame as text.
563            text.append(documentAsText());
564        }
565        if (callback.arg2 != 0) {
566            // Dump child frames as text.
567            text.append(childFramesAsText());
568        }
569        callback.obj = text.toString();
570        callback.sendToTarget();
571    }
572
573    /**
574     * Return the text drawn on the screen as a string
575     */
576    private native String documentAsText();
577
578    /**
579     * Return the text drawn on the child frames as a string
580     */
581    private native String childFramesAsText();
582
583    /*
584     * This method is called by WebCore to inform the frame that
585     * the Javascript window object has been cleared.
586     * We should re-attach any attached js interfaces.
587     */
588    private void windowObjectCleared(int nativeFramePointer) {
589        Iterator<String> iter = mJavaScriptObjects.keySet().iterator();
590        while (iter.hasNext())  {
591            String interfaceName = iter.next();
592            Object object = mJavaScriptObjects.get(interfaceName);
593            if (object != null) {
594                nativeAddJavascriptInterface(nativeFramePointer,
595                        mJavaScriptObjects.get(interfaceName), interfaceName);
596            }
597        }
598        mRemovedJavaScriptObjects.clear();
599
600        stringByEvaluatingJavaScriptFromString(SearchBoxImpl.JS_BRIDGE);
601    }
602
603    /**
604     * This method is called by WebCore to check whether application
605     * wants to hijack url loading
606     */
607    public boolean handleUrl(String url) {
608        if (mLoadInitFromJava == true) {
609            return false;
610        }
611        if (mCallbackProxy.shouldOverrideUrlLoading(url)) {
612            // if the url is hijacked, reset the state of the BrowserFrame
613            didFirstLayout();
614            return true;
615        } else {
616            return false;
617        }
618    }
619
620    public void addJavascriptInterface(Object obj, String interfaceName) {
621        assert obj != null;
622        removeJavascriptInterface(interfaceName);
623
624        mJavaScriptObjects.put(interfaceName, obj);
625    }
626
627    public void removeJavascriptInterface(String interfaceName) {
628        // We keep a reference to the removed object because the native side holds only a weak
629        // reference and we need to allow the object to continue to be used until the page has been
630        // navigated.
631        if (mJavaScriptObjects.containsKey(interfaceName)) {
632            mRemovedJavaScriptObjects.add(mJavaScriptObjects.remove(interfaceName));
633        }
634    }
635
636    /**
637     * Called by JNI.  Given a URI, find the associated file and return its size
638     * @param uri A String representing the URI of the desired file.
639     * @return int The size of the given file.
640     */
641    private int getFileSize(String uri) {
642        int size = 0;
643        try {
644            InputStream stream = mContext.getContentResolver()
645                            .openInputStream(Uri.parse(uri));
646            size = stream.available();
647            stream.close();
648        } catch (Exception e) {}
649        return size;
650    }
651
652    /**
653     * Called by JNI.  Given a URI, a buffer, and an offset into the buffer,
654     * copy the resource into buffer.
655     * @param uri A String representing the URI of the desired file.
656     * @param buffer The byte array to copy the data into.
657     * @param offset The offet into buffer to place the data.
658     * @param expectedSize The size that the buffer has allocated for this file.
659     * @return int The size of the given file, or zero if it fails.
660     */
661    private int getFile(String uri, byte[] buffer, int offset,
662            int expectedSize) {
663        int size = 0;
664        try {
665            InputStream stream = mContext.getContentResolver()
666                            .openInputStream(Uri.parse(uri));
667            size = stream.available();
668            if (size <= expectedSize && buffer != null
669                    && buffer.length - offset >= size) {
670                stream.read(buffer, offset, size);
671            } else {
672                size = 0;
673            }
674            stream.close();
675        } catch (java.io.FileNotFoundException e) {
676            Log.e(LOGTAG, "FileNotFoundException:" + e);
677            size = 0;
678        } catch (java.io.IOException e2) {
679            Log.e(LOGTAG, "IOException: " + e2);
680            size = 0;
681        }
682        return size;
683    }
684
685    /**
686     * Get the InputStream for an Android resource
687     * There are three different kinds of android resources:
688     * - file:///android_res
689     * - file:///android_asset
690     * - content://
691     * @param url The url to load.
692     * @return An InputStream to the android resource
693     */
694    private InputStream inputStreamForAndroidResource(String url) {
695        // This list needs to be kept in sync with the list in
696        // external/webkit/WebKit/android/WebCoreSupport/WebUrlLoaderClient.cpp
697        final String ANDROID_ASSET = "file:///android_asset/";
698        final String ANDROID_RESOURCE = "file:///android_res/";
699        final String ANDROID_CONTENT = "content:";
700
701        // file:///android_res
702        if (url.startsWith(ANDROID_RESOURCE)) {
703            url = url.replaceFirst(ANDROID_RESOURCE, "");
704            if (url == null || url.length() == 0) {
705                Log.e(LOGTAG, "url has length 0 " + url);
706                return null;
707            }
708            int slash = url.indexOf('/');
709            int dot = url.indexOf('.', slash);
710            if (slash == -1 || dot == -1) {
711                Log.e(LOGTAG, "Incorrect res path: " + url);
712                return null;
713            }
714            String subClassName = url.substring(0, slash);
715            String fieldName = url.substring(slash + 1, dot);
716            String errorMsg = null;
717            try {
718                final Class<?> d = mContext.getApplicationContext()
719                        .getClassLoader().loadClass(
720                                mContext.getPackageName() + ".R$"
721                                        + subClassName);
722                final java.lang.reflect.Field field = d.getField(fieldName);
723                final int id = field.getInt(null);
724                TypedValue value = new TypedValue();
725                mContext.getResources().getValue(id, value, true);
726                if (value.type == TypedValue.TYPE_STRING) {
727                    return mContext.getAssets().openNonAsset(
728                            value.assetCookie, value.string.toString(),
729                            AssetManager.ACCESS_STREAMING);
730                } else {
731                    // Old stack only supports TYPE_STRING for res files
732                    Log.e(LOGTAG, "not of type string: " + url);
733                    return null;
734                }
735            } catch (Exception e) {
736                Log.e(LOGTAG, "Exception: " + url);
737                return null;
738            }
739
740        // file:///android_asset
741        } else if (url.startsWith(ANDROID_ASSET)) {
742            url = url.replaceFirst(ANDROID_ASSET, "");
743            try {
744                AssetManager assets = mContext.getAssets();
745                Uri uri = Uri.parse(url);
746                return assets.open(uri.getPath(), AssetManager.ACCESS_STREAMING);
747            } catch (IOException e) {
748                return null;
749            }
750
751        // content://
752        } else if (mSettings.getAllowContentAccess() &&
753                   url.startsWith(ANDROID_CONTENT)) {
754            try {
755                // Strip off MIME type. If we don't do this, we can fail to
756                // load Gmail attachments, because the URL being loaded doesn't
757                // exactly match the URL we have permission to read.
758                int mimeIndex = url.lastIndexOf('?');
759                if (mimeIndex != -1) {
760                    url = url.substring(0, mimeIndex);
761                }
762                Uri uri = Uri.parse(url);
763                return mContext.getContentResolver().openInputStream(uri);
764            } catch (Exception e) {
765                Log.e(LOGTAG, "Exception: " + url);
766                return null;
767            }
768        } else {
769            return null;
770        }
771    }
772
773    /**
774     * If this looks like a POST request (form submission) containing a username
775     * and password, give the user the option of saving them. Will either do
776     * nothing, or block until the UI interaction is complete.
777     *
778     * Called directly by WebKit.
779     *
780     * @param postData The data about to be sent as the body of a POST request.
781     * @param username The username entered by the user (sniffed from the DOM).
782     * @param password The password entered by the user (sniffed from the DOM).
783     */
784    private void maybeSavePassword(
785            byte[] postData, String username, String password) {
786        if (postData == null
787                || username == null || username.isEmpty()
788                || password == null || password.isEmpty()) {
789            return; // No password to save.
790        }
791
792        if (!mSettings.getSavePassword()) {
793            return; // User doesn't want to save passwords.
794        }
795
796        try {
797            if (DebugFlags.BROWSER_FRAME) {
798                Assert.assertNotNull(mCallbackProxy.getBackForwardList()
799                        .getCurrentItem());
800            }
801            WebAddress uri = new WebAddress(mCallbackProxy
802                    .getBackForwardList().getCurrentItem().getUrl());
803            String schemePlusHost = uri.getScheme() + uri.getHost();
804            // Check to see if the username & password appear in
805            // the post data (there could be another form on the
806            // page and that was posted instead.
807            String postString = new String(postData);
808            if (postString.contains(URLEncoder.encode(username)) &&
809                    postString.contains(URLEncoder.encode(password))) {
810                String[] saved = mDatabase.getUsernamePassword(
811                        schemePlusHost);
812                if (saved != null) {
813                    // null username implies that user has chosen not to
814                    // save password
815                    if (saved[0] != null) {
816                        // non-null username implies that user has
817                        // chosen to save password, so update the
818                        // recorded password
819                        mDatabase.setUsernamePassword(
820                                schemePlusHost, username, password);
821                    }
822                } else {
823                    // CallbackProxy will handle creating the resume
824                    // message
825                    mCallbackProxy.onSavePassword(schemePlusHost, username,
826                            password, null);
827                }
828            }
829        } catch (ParseException ex) {
830            // if it is bad uri, don't save its password
831        }
832    }
833
834    // Called by jni from the chrome network stack.
835    private WebResourceResponse shouldInterceptRequest(String url) {
836        InputStream androidResource = inputStreamForAndroidResource(url);
837        if (androidResource != null) {
838            return new WebResourceResponse(null, null, androidResource);
839        }
840
841        // Note that we check this after looking for an android_asset or
842        // android_res URL, as we allow those even if file access is disabled.
843        if (!mSettings.getAllowFileAccess() && url.startsWith("file://")) {
844            return new WebResourceResponse(null, null, null);
845        }
846
847        WebResourceResponse response = mCallbackProxy.shouldInterceptRequest(url);
848        if (response == null && "browser:incognito".equals(url)) {
849            try {
850                Resources res = mContext.getResources();
851                InputStream ins = res.openRawResource(
852                        com.android.internal.R.raw.incognito_mode_start_page);
853                response = new WebResourceResponse("text/html", "utf8", ins);
854            } catch (NotFoundException ex) {
855                // This shouldn't happen, but try and gracefully handle it jic
856                Log.w(LOGTAG, "Failed opening raw.incognito_mode_start_page", ex);
857            }
858        }
859        return response;
860    }
861
862    /**
863     * Set the progress for the browser activity.  Called by native code.
864     * Uses a delay so it does not happen too often.
865     * @param newProgress An int between zero and one hundred representing
866     *                    the current progress percentage of loading the page.
867     */
868    private void setProgress(int newProgress) {
869        mCallbackProxy.onProgressChanged(newProgress);
870        if (newProgress == 100) {
871            sendMessageDelayed(obtainMessage(FRAME_COMPLETED), 100);
872        }
873        // FIXME: Need to figure out a better way to switch out of the history
874        // drawing mode. Maybe we can somehow compare the history picture with
875        // the current picture, and switch when it contains more content.
876        if (mFirstLayoutDone && newProgress > TRANSITION_SWITCH_THRESHOLD) {
877            mCallbackProxy.switchOutDrawHistory();
878        }
879    }
880
881    /**
882     * Send the icon to the activity for display.
883     * @param icon A Bitmap representing a page's favicon.
884     */
885    private void didReceiveIcon(Bitmap icon) {
886        mCallbackProxy.onReceivedIcon(icon);
887    }
888
889    // Called by JNI when an apple-touch-icon attribute was found.
890    private void didReceiveTouchIconUrl(String url, boolean precomposed) {
891        mCallbackProxy.onReceivedTouchIconUrl(url, precomposed);
892    }
893
894    /**
895     * Request a new window from the client.
896     * @return The BrowserFrame object stored in the new WebView.
897     */
898    private BrowserFrame createWindow(boolean dialog, boolean userGesture) {
899        return mCallbackProxy.createWindow(dialog, userGesture);
900    }
901
902    /**
903     * Try to focus this WebView.
904     */
905    private void requestFocus() {
906        mCallbackProxy.onRequestFocus();
907    }
908
909    /**
910     * Close this frame and window.
911     */
912    private void closeWindow(WebViewCore w) {
913        mCallbackProxy.onCloseWindow(w.getWebView());
914    }
915
916    // XXX: Must match PolicyAction in FrameLoaderTypes.h in webcore
917    static final int POLICY_USE = 0;
918    static final int POLICY_IGNORE = 2;
919
920    private void decidePolicyForFormResubmission(int policyFunction) {
921        Message dontResend = obtainMessage(POLICY_FUNCTION, policyFunction,
922                POLICY_IGNORE);
923        Message resend = obtainMessage(POLICY_FUNCTION, policyFunction,
924                POLICY_USE);
925        mCallbackProxy.onFormResubmission(dontResend, resend);
926    }
927
928    /**
929     * Tell the activity to update its global history.
930     */
931    private void updateVisitedHistory(String url, boolean isReload) {
932        mCallbackProxy.doUpdateVisitedHistory(url, isReload);
933    }
934
935    /**
936     * Get the CallbackProxy for sending messages to the UI thread.
937     */
938    /* package */ CallbackProxy getCallbackProxy() {
939        return mCallbackProxy;
940    }
941
942    /**
943     * Returns the User Agent used by this frame
944     */
945    String getUserAgentString() {
946        return mSettings.getUserAgentString();
947    }
948
949    // These ids need to be in sync with enum rawResId in PlatformBridge.h
950    private static final int NODOMAIN = 1;
951    private static final int LOADERROR = 2;
952    /* package */ static final int DRAWABLEDIR = 3;
953    private static final int FILE_UPLOAD_LABEL = 4;
954    private static final int RESET_LABEL = 5;
955    private static final int SUBMIT_LABEL = 6;
956    private static final int FILE_UPLOAD_NO_FILE_CHOSEN = 7;
957
958    private String getRawResFilename(int id) {
959        return getRawResFilename(id, mContext);
960    }
961    /* package */ static String getRawResFilename(int id, Context context) {
962        int resid;
963        switch (id) {
964            case NODOMAIN:
965                resid = com.android.internal.R.raw.nodomain;
966                break;
967
968            case LOADERROR:
969                resid = com.android.internal.R.raw.loaderror;
970                break;
971
972            case DRAWABLEDIR:
973                // use one known resource to find the drawable directory
974                resid = com.android.internal.R.drawable.btn_check_off;
975                break;
976
977            case FILE_UPLOAD_LABEL:
978                return context.getResources().getString(
979                        com.android.internal.R.string.upload_file);
980
981            case RESET_LABEL:
982                return context.getResources().getString(
983                        com.android.internal.R.string.reset);
984
985            case SUBMIT_LABEL:
986                return context.getResources().getString(
987                        com.android.internal.R.string.submit);
988
989            case FILE_UPLOAD_NO_FILE_CHOSEN:
990                return context.getResources().getString(
991                        com.android.internal.R.string.no_file_chosen);
992
993            default:
994                Log.e(LOGTAG, "getRawResFilename got incompatible resource ID");
995                return "";
996        }
997        TypedValue value = new TypedValue();
998        context.getResources().getValue(resid, value, true);
999        if (id == DRAWABLEDIR) {
1000            String path = value.string.toString();
1001            int index = path.lastIndexOf('/');
1002            if (index < 0) {
1003                Log.e(LOGTAG, "Can't find drawable directory.");
1004                return "";
1005            }
1006            return path.substring(0, index + 1);
1007        }
1008        return value.string.toString();
1009    }
1010
1011    private float density() {
1012        return mContext.getResources().getDisplayMetrics().density;
1013    }
1014
1015    /**
1016     * Called by JNI when the native HTTP stack gets an authentication request.
1017     *
1018     * We delegate the request to CallbackProxy, and route its response to
1019     * {@link #nativeAuthenticationProceed(int, String, String)} or
1020     * {@link #nativeAuthenticationCancel(int)}.
1021     *
1022     * We don't care what thread the callback is invoked on. All threading is
1023     * handled on the C++ side, because the WebKit thread may be blocked on a
1024     * synchronous call and unable to pump our MessageQueue.
1025     */
1026    private void didReceiveAuthenticationChallenge(
1027            final int handle, String host, String realm, final boolean useCachedCredentials,
1028            final boolean suppressDialog) {
1029
1030        HttpAuthHandler handler = new HttpAuthHandler() {
1031
1032            @Override
1033            public boolean useHttpAuthUsernamePassword() {
1034                return useCachedCredentials;
1035            }
1036
1037            @Override
1038            public void proceed(String username, String password) {
1039                nativeAuthenticationProceed(handle, username, password);
1040            }
1041
1042            @Override
1043            public void cancel() {
1044                nativeAuthenticationCancel(handle);
1045            }
1046
1047            @Override
1048            public boolean suppressDialog() {
1049                return suppressDialog;
1050            }
1051        };
1052        mCallbackProxy.onReceivedHttpAuthRequest(handler, host, realm);
1053    }
1054
1055    /**
1056     * Called by JNI when the Chromium HTTP stack gets an invalid certificate chain.
1057     *
1058     * We delegate the request to CallbackProxy, and route its response to
1059     * {@link #nativeSslCertErrorProceed(int)} or
1060     * {@link #nativeSslCertErrorCancel(int, int)}.
1061     */
1062    private void reportSslCertError(final int handle, final int certError, byte certDER[],
1063            String url) {
1064        final SslError sslError;
1065        try {
1066            X509Certificate cert = new X509CertImpl(certDER);
1067            SslCertificate sslCert = new SslCertificate(cert);
1068            sslError = SslError.SslErrorFromChromiumErrorCode(certError, sslCert, url);
1069        } catch (IOException e) {
1070            // Can't get the certificate, not much to do.
1071            Log.e(LOGTAG, "Can't get the certificate from WebKit, canceling");
1072            nativeSslCertErrorCancel(handle, certError);
1073            return;
1074        }
1075
1076        if (SslCertLookupTable.getInstance().isAllowed(sslError)) {
1077            nativeSslCertErrorProceed(handle);
1078            mCallbackProxy.onProceededAfterSslError(sslError);
1079            return;
1080        }
1081
1082        SslErrorHandler handler = new SslErrorHandler() {
1083            @Override
1084            public void proceed() {
1085                SslCertLookupTable.getInstance().setIsAllowed(sslError);
1086                post(new Runnable() {
1087                        public void run() {
1088                            nativeSslCertErrorProceed(handle);
1089                        }
1090                    });
1091            }
1092            @Override
1093            public void cancel() {
1094                post(new Runnable() {
1095                        public void run() {
1096                            nativeSslCertErrorCancel(handle, certError);
1097                        }
1098                    });
1099            }
1100        };
1101        mCallbackProxy.onReceivedSslError(handler, sslError);
1102    }
1103
1104    /**
1105     * Called by JNI when the native HTTPS stack gets a client
1106     * certificate request.
1107     *
1108     * We delegate the request to CallbackProxy, and route its response to
1109     * {@link #nativeSslClientCert(int, X509Certificate)}.
1110     */
1111    private void requestClientCert(int handle, String hostAndPort) {
1112        SslClientCertLookupTable table = SslClientCertLookupTable.getInstance();
1113        if (table.IsAllowed(hostAndPort)) {
1114            // previously allowed
1115            nativeSslClientCert(handle,
1116                                table.PrivateKey(hostAndPort),
1117                                table.CertificateChain(hostAndPort));
1118        } else if (table.IsDenied(hostAndPort)) {
1119            // previously denied
1120            nativeSslClientCert(handle, null, null);
1121        } else {
1122            // previously ignored or new
1123            mCallbackProxy.onReceivedClientCertRequest(
1124                    new ClientCertRequestHandler(this, handle, hostAndPort, table), hostAndPort);
1125        }
1126    }
1127
1128    /**
1129     * Called by JNI when the native HTTP stack needs to download a file.
1130     *
1131     * We delegate the request to CallbackProxy, which owns the current app's
1132     * DownloadListener.
1133     */
1134    private void downloadStart(String url, String userAgent,
1135            String contentDisposition, String mimeType, long contentLength) {
1136        // This will only work if the url ends with the filename
1137        if (mimeType.isEmpty()) {
1138            try {
1139                String extension = url.substring(url.lastIndexOf('.') + 1);
1140                mimeType = libcore.net.MimeUtils.guessMimeTypeFromExtension(extension);
1141                // MimeUtils might return null, not sure if downloadmanager is happy with that
1142                if (mimeType == null)
1143                    mimeType = "";
1144            } catch(IndexOutOfBoundsException exception) {
1145                // mimeType string end with a '.', not much to do
1146            }
1147        }
1148        mimeType = MimeTypeMap.getSingleton().remapGenericMimeType(
1149                mimeType, url, contentDisposition);
1150
1151        if (CertTool.getCertType(mimeType) != null) {
1152            mKeyStoreHandler = new KeyStoreHandler(mimeType);
1153        } else {
1154            mCallbackProxy.onDownloadStart(url, userAgent,
1155                contentDisposition, mimeType, contentLength);
1156        }
1157    }
1158
1159    /**
1160     * Called by JNI for Chrome HTTP stack when the Java side needs to access the data.
1161     */
1162    private void didReceiveData(byte data[], int size) {
1163        if (mKeyStoreHandler != null) mKeyStoreHandler.didReceiveData(data, size);
1164    }
1165
1166    private void didFinishLoading() {
1167      if (mKeyStoreHandler != null) {
1168          mKeyStoreHandler.installCert(mContext);
1169          mKeyStoreHandler = null;
1170      }
1171    }
1172
1173    /**
1174     * Called by JNI when we recieve a certificate for the page's main resource.
1175     * Used by the Chromium HTTP stack only.
1176     */
1177    private void setCertificate(byte cert_der[]) {
1178        try {
1179            X509Certificate cert = new X509CertImpl(cert_der);
1180            mCallbackProxy.onReceivedCertificate(new SslCertificate(cert));
1181        } catch (IOException e) {
1182            // Can't get the certificate, not much to do.
1183            Log.e(LOGTAG, "Can't get the certificate from WebKit, canceling");
1184            return;
1185        }
1186    }
1187
1188    /*package*/ SearchBox getSearchBox() {
1189        return mSearchBox;
1190    }
1191
1192    /**
1193     * Called by JNI when processing the X-Auto-Login header.
1194     */
1195    private void autoLogin(String realm, String account, String args) {
1196        mCallbackProxy.onReceivedLoginRequest(realm, account, args);
1197    }
1198
1199    //==========================================================================
1200    // native functions
1201    //==========================================================================
1202
1203    /**
1204     * Create a new native frame for a given WebView
1205     * @param w     A WebView that the frame draws into.
1206     * @param am    AssetManager to use to get assets.
1207     * @param list  The native side will add and remove items from this list as
1208     *              the native list changes.
1209     */
1210    private native void nativeCreateFrame(WebViewCore w, AssetManager am,
1211            WebBackForwardList list);
1212
1213    /**
1214     * Destroy the native frame.
1215     */
1216    public native void nativeDestroyFrame();
1217
1218    private native void nativeCallPolicyFunction(int policyFunction,
1219            int decision);
1220
1221    /**
1222     * Reload the current main frame.
1223     */
1224    public native void reload(boolean allowStale);
1225
1226    /**
1227     * Go back or forward the number of steps given.
1228     * @param steps A negative or positive number indicating the direction
1229     *              and number of steps to move.
1230     */
1231    private native void nativeGoBackOrForward(int steps);
1232
1233    /**
1234     * stringByEvaluatingJavaScriptFromString will execute the
1235     * JS passed in in the context of this browser frame.
1236     * @param script A javascript string to execute
1237     *
1238     * @return string result of execution or null
1239     */
1240    public native String stringByEvaluatingJavaScriptFromString(String script);
1241
1242    /**
1243     * Add a javascript interface to the main frame.
1244     */
1245    private native void nativeAddJavascriptInterface(int nativeFramePointer,
1246            Object obj, String interfaceName);
1247
1248    public native void clearCache();
1249
1250    /**
1251     * Returns false if the url is bad.
1252     */
1253    private native void nativeLoadUrl(String url, Map<String, String> headers);
1254
1255    private native void nativePostUrl(String url, byte[] postData);
1256
1257    private native void nativeLoadData(String baseUrl, String data,
1258            String mimeType, String encoding, String historyUrl);
1259
1260    /**
1261     * Stop loading the current page.
1262     */
1263    public void stopLoading() {
1264        if (mIsMainFrame) {
1265            resetLoadingStates();
1266        }
1267        nativeStopLoading();
1268    }
1269
1270    private native void nativeStopLoading();
1271
1272    /**
1273     * Return true if the document has images.
1274     */
1275    public native boolean documentHasImages();
1276
1277    /**
1278     * @return TRUE if there is a password field in the current frame
1279     */
1280    private native boolean hasPasswordField();
1281
1282    /**
1283     * Get username and password in the current frame. If found, String[0] is
1284     * username and String[1] is password. Otherwise return NULL.
1285     * @return String[]
1286     */
1287    private native String[] getUsernamePassword();
1288
1289    /**
1290     * Set username and password to the proper fields in the current frame
1291     * @param username
1292     * @param password
1293     */
1294    private native void setUsernamePassword(String username, String password);
1295
1296    private native String nativeSaveWebArchive(String basename, boolean autoname);
1297
1298    private native void nativeOrientationChanged(int orientation);
1299
1300    private native void nativeAuthenticationProceed(int handle, String username, String password);
1301    private native void nativeAuthenticationCancel(int handle);
1302
1303    private native void nativeSslCertErrorProceed(int handle);
1304    private native void nativeSslCertErrorCancel(int handle, int certError);
1305
1306    native void nativeSslClientCert(int handle,
1307                                    byte[] pkcs8EncodedPrivateKey,
1308                                    byte[][] asn1DerEncodedCertificateChain);
1309
1310    /**
1311     * Returns true when the contents of the frame is an RTL or vertical-rl
1312     * page. This is used for determining whether a frame should be initially
1313     * scrolled right-most as opposed to left-most.
1314     * @return true when the frame should be initially scrolled right-most
1315     * based on the text direction and writing mode.
1316     */
1317    /* package */ boolean getShouldStartScrolledRight() {
1318        return nativeGetShouldStartScrolledRight(mNativeFrame);
1319    }
1320
1321    private native boolean nativeGetShouldStartScrolledRight(int nativeBrowserFrame);
1322}
1323