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