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 17 package android.webkit; 18 19 import android.app.ActivityManager; 20 import android.content.ComponentCallbacks; 21 import android.content.Context; 22 import android.content.res.AssetManager; 23 import android.content.res.Configuration; 24 import android.content.res.Resources; 25 import android.content.res.Resources.NotFoundException; 26 import android.graphics.Bitmap; 27 import android.net.ParseException; 28 import android.net.Uri; 29 import android.net.WebAddress; 30 import android.net.http.ErrorStrings; 31 import android.net.http.SslCertificate; 32 import android.net.http.SslError; 33 import android.os.Handler; 34 import android.os.Message; 35 import android.util.Log; 36 import android.util.TypedValue; 37 import android.view.Surface; 38 import android.view.ViewRootImpl; 39 import android.view.WindowManager; 40 41 import junit.framework.Assert; 42 43 import java.io.IOException; 44 import java.io.InputStream; 45 import java.lang.ref.WeakReference; 46 import java.net.URLEncoder; 47 import java.nio.charset.Charsets; 48 import java.security.PrivateKey; 49 import java.security.cert.CertificateEncodingException; 50 import java.security.cert.X509Certificate; 51 import java.util.ArrayList; 52 import java.util.HashMap; 53 import java.util.HashSet; 54 import java.util.Iterator; 55 import java.util.Map; 56 import java.util.Set; 57 58 import org.apache.harmony.security.provider.cert.X509CertImpl; 59 import org.apache.harmony.xnet.provider.jsse.OpenSSLDSAPrivateKey; 60 import org.apache.harmony.xnet.provider.jsse.OpenSSLRSAPrivateKey; 61 62 class 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