BrowserFrame.java revision 2376f8253f59e70e733bdf338e9cb49535f3d50f
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.URL; 47import java.net.URLEncoder; 48import java.nio.charset.Charsets; 49import java.security.PrivateKey; 50import java.security.cert.CertificateEncodingException; 51import java.security.cert.X509Certificate; 52import java.util.ArrayList; 53import java.util.HashMap; 54import java.util.HashSet; 55import java.util.Iterator; 56import java.util.Map; 57import java.util.Set; 58 59import org.apache.harmony.security.provider.cert.X509CertImpl; 60 61class BrowserFrame extends Handler { 62 63 private static final String LOGTAG = "webkit"; 64 65 /** 66 * Cap the number of LoadListeners that will be instantiated, so 67 * we don't blow the GREF count. Attempting to queue more than 68 * this many requests will prompt an error() callback on the 69 * request's LoadListener 70 */ 71 private final static int MAX_OUTSTANDING_REQUESTS = 300; 72 73 private final CallbackProxy mCallbackProxy; 74 private final WebSettings mSettings; 75 private final Context mContext; 76 private final WebViewDatabase mDatabase; 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 WebSettings 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 mDatabase = WebViewDatabase.getInstance(appContext); 246 mWebViewCore = w; 247 248 mSearchBox = new SearchBoxImpl(mWebViewCore, mCallbackProxy); 249 mJavaScriptObjects.put(SearchBoxImpl.JS_INTERFACE_NAME, mSearchBox); 250 251 AssetManager am = context.getAssets(); 252 nativeCreateFrame(w, am, proxy.getBackForwardList()); 253 254 if (DebugFlags.BROWSER_FRAME) { 255 Log.v(LOGTAG, "BrowserFrame constructor: this=" + this); 256 } 257 } 258 259 /** 260 * Load a url from the network or the filesystem into the main frame. 261 * Following the same behaviour as Safari, javascript: URLs are not passed 262 * to the main frame, instead they are evaluated immediately. 263 * @param url The url to load. 264 * @param extraHeaders The extra headers sent with this url. This should not 265 * include the common headers like "user-agent". If it does, it 266 * will be replaced by the intrinsic value of the WebView. 267 */ 268 public void loadUrl(String url, Map<String, String> extraHeaders) { 269 mLoadInitFromJava = true; 270 if (URLUtil.isJavaScriptUrl(url)) { 271 // strip off the scheme and evaluate the string 272 stringByEvaluatingJavaScriptFromString( 273 url.substring("javascript:".length())); 274 } else { 275 nativeLoadUrl(url, extraHeaders); 276 } 277 mLoadInitFromJava = false; 278 } 279 280 /** 281 * Load a url with "POST" method from the network into the main frame. 282 * @param url The url to load. 283 * @param data The data for POST request. 284 */ 285 public void postUrl(String url, byte[] data) { 286 mLoadInitFromJava = true; 287 nativePostUrl(url, data); 288 mLoadInitFromJava = false; 289 } 290 291 /** 292 * Load the content as if it was loaded by the provided base URL. The 293 * historyUrl is used as the history entry for the load data. 294 * 295 * @param baseUrl Base URL used to resolve relative paths in the content 296 * @param data Content to render in the browser 297 * @param mimeType Mimetype of the data being passed in 298 * @param encoding Character set encoding of the provided data. 299 * @param historyUrl URL to use as the history entry. 300 */ 301 public void loadData(String baseUrl, String data, String mimeType, 302 String encoding, String historyUrl) { 303 mLoadInitFromJava = true; 304 if (historyUrl == null || historyUrl.length() == 0) { 305 historyUrl = "about:blank"; 306 } 307 if (data == null) { 308 data = ""; 309 } 310 311 // Setup defaults for missing values. These defaults where taken from 312 // WebKit's WebFrame.mm 313 if (baseUrl == null || baseUrl.length() == 0) { 314 baseUrl = "about:blank"; 315 } 316 if (mimeType == null || mimeType.length() == 0) { 317 mimeType = "text/html"; 318 } 319 nativeLoadData(baseUrl, data, mimeType, encoding, historyUrl); 320 mLoadInitFromJava = false; 321 } 322 323 /** 324 * Saves the contents of the frame as a web archive. 325 * 326 * @param basename The filename where the archive should be placed. 327 * @param autoname If false, takes filename to be a file. If true, filename 328 * is assumed to be a directory in which a filename will be 329 * chosen according to the url of the current page. 330 */ 331 /* package */ String saveWebArchive(String basename, boolean autoname) { 332 return nativeSaveWebArchive(basename, autoname); 333 } 334 335 /** 336 * Go back or forward the number of steps given. 337 * @param steps A negative or positive number indicating the direction 338 * and number of steps to move. 339 */ 340 public void goBackOrForward(int steps) { 341 mLoadInitFromJava = true; 342 nativeGoBackOrForward(steps); 343 mLoadInitFromJava = false; 344 } 345 346 /** 347 * native callback 348 * Report an error to an activity. 349 * @param errorCode The HTTP error code. 350 * @param description Optional human-readable description. If no description 351 * is given, we'll use a standard localized error message. 352 * @param failingUrl The URL that was being loaded when the error occurred. 353 * TODO: Report all errors including resource errors but include some kind 354 * of domain identifier. Change errorCode to an enum for a cleaner 355 * interface. 356 */ 357 private void reportError(int errorCode, String description, String failingUrl) { 358 // As this is called for the main resource and loading will be stopped 359 // after, reset the state variables. 360 resetLoadingStates(); 361 if (description == null || description.isEmpty()) { 362 description = ErrorStrings.getString(errorCode, mContext); 363 } 364 mCallbackProxy.onReceivedError(errorCode, description, failingUrl); 365 } 366 367 private void resetLoadingStates() { 368 mCommitted = true; 369 mFirstLayoutDone = true; 370 } 371 372 /* package */boolean committed() { 373 return mCommitted; 374 } 375 376 /* package */boolean firstLayoutDone() { 377 return mFirstLayoutDone; 378 } 379 380 /* package */int loadType() { 381 return mLoadType; 382 } 383 384 /* package */void didFirstLayout() { 385 if (!mFirstLayoutDone) { 386 mFirstLayoutDone = true; 387 // ensure {@link WebViewCore#webkitDraw} is called as we were 388 // blocking the update in {@link #loadStarted} 389 mWebViewCore.contentDraw(); 390 } 391 } 392 393 /** 394 * native callback 395 * Indicates the beginning of a new load. 396 * This method will be called once for the main frame. 397 */ 398 private void loadStarted(String url, Bitmap favicon, int loadType, 399 boolean isMainFrame) { 400 mIsMainFrame = isMainFrame; 401 402 if (isMainFrame || loadType == FRAME_LOADTYPE_STANDARD) { 403 mLoadType = loadType; 404 405 if (isMainFrame) { 406 // Call onPageStarted for main frames. 407 mCallbackProxy.onPageStarted(url, favicon); 408 // as didFirstLayout() is only called for the main frame, reset 409 // mFirstLayoutDone only for the main frames 410 mFirstLayoutDone = false; 411 mCommitted = false; 412 // remove pending draw to block update until mFirstLayoutDone is 413 // set to true in didFirstLayout() 414 mWebViewCore.removeMessages(WebViewCore.EventHub.WEBKIT_DRAW); 415 } 416 } 417 } 418 419 @SuppressWarnings("unused") 420 private void saveFormData(HashMap<String, String> data) { 421 if (mSettings.getSaveFormData()) { 422 final WebHistoryItem h = mCallbackProxy.getBackForwardList() 423 .getCurrentItem(); 424 if (h != null) { 425 String url = WebTextView.urlForAutoCompleteData(h.getUrl()); 426 if (url != null) { 427 mDatabase.setFormData(url, data); 428 } 429 } 430 } 431 } 432 433 @SuppressWarnings("unused") 434 private boolean shouldSaveFormData() { 435 if (mSettings.getSaveFormData()) { 436 final WebHistoryItem h = mCallbackProxy.getBackForwardList() 437 .getCurrentItem(); 438 return h != null && h.getUrl() != null; 439 } 440 return false; 441 } 442 443 /** 444 * native callback 445 * Indicates the WebKit has committed to the new load 446 */ 447 private void transitionToCommitted(int loadType, boolean isMainFrame) { 448 // loadType is not used yet 449 if (isMainFrame) { 450 mCommitted = true; 451 mWebViewCore.getWebView().mViewManager.postResetStateAll(); 452 } 453 } 454 455 /** 456 * native callback 457 * <p> 458 * Indicates the end of a new load. 459 * This method will be called once for the main frame. 460 */ 461 private void loadFinished(String url, int loadType, boolean isMainFrame) { 462 // mIsMainFrame and isMainFrame are better be equal!!! 463 464 if (isMainFrame || loadType == FRAME_LOADTYPE_STANDARD) { 465 if (isMainFrame) { 466 resetLoadingStates(); 467 mCallbackProxy.switchOutDrawHistory(); 468 mCallbackProxy.onPageFinished(url); 469 } 470 } 471 } 472 473 /** 474 * We have received an SSL certificate for the main top-level page. 475 * 476 * !!!Called from the network thread!!! 477 */ 478 void certificate(SslCertificate certificate) { 479 if (mIsMainFrame) { 480 // we want to make this call even if the certificate is null 481 // (ie, the site is not secure) 482 mCallbackProxy.onReceivedCertificate(certificate); 483 } 484 } 485 486 /** 487 * Destroy all native components of the BrowserFrame. 488 */ 489 public void destroy() { 490 nativeDestroyFrame(); 491 mBlockMessages = true; 492 removeCallbacksAndMessages(null); 493 } 494 495 /** 496 * Handle messages posted to us. 497 * @param msg The message to handle. 498 */ 499 @Override 500 public void handleMessage(Message msg) { 501 if (mBlockMessages) { 502 return; 503 } 504 switch (msg.what) { 505 case FRAME_COMPLETED: { 506 if (mSettings.getSavePassword() && hasPasswordField()) { 507 WebHistoryItem item = mCallbackProxy.getBackForwardList() 508 .getCurrentItem(); 509 if (item != null) { 510 WebAddress uri = new WebAddress(item.getUrl()); 511 String schemePlusHost = uri.getScheme() + uri.getHost(); 512 String[] up = 513 mDatabase.getUsernamePassword(schemePlusHost); 514 if (up != null && up[0] != null) { 515 setUsernamePassword(up[0], up[1]); 516 } 517 } 518 } 519 if (!JniUtil.useChromiumHttpStack()) { 520 WebViewWorker.getHandler().sendEmptyMessage( 521 WebViewWorker.MSG_TRIM_CACHE); 522 } 523 break; 524 } 525 526 case POLICY_FUNCTION: { 527 nativeCallPolicyFunction(msg.arg1, msg.arg2); 528 break; 529 } 530 531 case ORIENTATION_CHANGED: { 532 if (mOrientation != msg.arg1) { 533 mOrientation = msg.arg1; 534 nativeOrientationChanged(msg.arg1); 535 } 536 break; 537 } 538 539 default: 540 break; 541 } 542 } 543 544 /** 545 * Punch-through for WebCore to set the document 546 * title. Inform the Activity of the new title. 547 * @param title The new title of the document. 548 */ 549 private void setTitle(String title) { 550 // FIXME: The activity must call getTitle (a native method) to get the 551 // title. We should try and cache the title if we can also keep it in 552 // sync with the document. 553 mCallbackProxy.onReceivedTitle(title); 554 } 555 556 /** 557 * Retrieves the render tree of this frame and puts it as the object for 558 * the message and sends the message. 559 * @param callback the message to use to send the render tree 560 */ 561 public void externalRepresentation(Message callback) { 562 callback.obj = externalRepresentation();; 563 callback.sendToTarget(); 564 } 565 566 /** 567 * Return the render tree as a string 568 */ 569 private native String externalRepresentation(); 570 571 /** 572 * Retrieves the visual text of the frames, puts it as the object for 573 * the message and sends the message. 574 * @param callback the message to use to send the visual text 575 */ 576 public void documentAsText(Message callback) { 577 StringBuilder text = new StringBuilder(); 578 if (callback.arg1 != 0) { 579 // Dump top frame as text. 580 text.append(documentAsText()); 581 } 582 if (callback.arg2 != 0) { 583 // Dump child frames as text. 584 text.append(childFramesAsText()); 585 } 586 callback.obj = text.toString(); 587 callback.sendToTarget(); 588 } 589 590 /** 591 * Return the text drawn on the screen as a string 592 */ 593 private native String documentAsText(); 594 595 /** 596 * Return the text drawn on the child frames as a string 597 */ 598 private native String childFramesAsText(); 599 600 /* 601 * This method is called by WebCore to inform the frame that 602 * the Javascript window object has been cleared. 603 * We should re-attach any attached js interfaces. 604 */ 605 private void windowObjectCleared(int nativeFramePointer) { 606 Iterator<String> iter = mJavaScriptObjects.keySet().iterator(); 607 while (iter.hasNext()) { 608 String interfaceName = iter.next(); 609 Object object = mJavaScriptObjects.get(interfaceName); 610 if (object != null) { 611 nativeAddJavascriptInterface(nativeFramePointer, 612 mJavaScriptObjects.get(interfaceName), interfaceName); 613 } 614 } 615 mRemovedJavaScriptObjects.clear(); 616 617 stringByEvaluatingJavaScriptFromString(SearchBoxImpl.JS_BRIDGE); 618 } 619 620 /** 621 * This method is called by WebCore to check whether application 622 * wants to hijack url loading 623 */ 624 public boolean handleUrl(String url) { 625 if (mLoadInitFromJava == true) { 626 return false; 627 } 628 if (mCallbackProxy.shouldOverrideUrlLoading(url)) { 629 // if the url is hijacked, reset the state of the BrowserFrame 630 didFirstLayout(); 631 return true; 632 } else { 633 return false; 634 } 635 } 636 637 public void addJavascriptInterface(Object obj, String interfaceName) { 638 assert obj != null; 639 removeJavascriptInterface(interfaceName); 640 641 mJavaScriptObjects.put(interfaceName, obj); 642 } 643 644 public void removeJavascriptInterface(String interfaceName) { 645 // We keep a reference to the removed object because the native side holds only a weak 646 // reference and we need to allow the object to continue to be used until the page has been 647 // navigated. 648 if (mJavaScriptObjects.containsKey(interfaceName)) { 649 mRemovedJavaScriptObjects.add(mJavaScriptObjects.remove(interfaceName)); 650 } 651 } 652 653 /** 654 * Called by JNI. Given a URI, find the associated file and return its size 655 * @param uri A String representing the URI of the desired file. 656 * @return int The size of the given file. 657 */ 658 private int getFileSize(String uri) { 659 int size = 0; 660 try { 661 InputStream stream = mContext.getContentResolver() 662 .openInputStream(Uri.parse(uri)); 663 size = stream.available(); 664 stream.close(); 665 } catch (Exception e) {} 666 return size; 667 } 668 669 /** 670 * Called by JNI. Given a URI, a buffer, and an offset into the buffer, 671 * copy the resource into buffer. 672 * @param uri A String representing the URI of the desired file. 673 * @param buffer The byte array to copy the data into. 674 * @param offset The offet into buffer to place the data. 675 * @param expectedSize The size that the buffer has allocated for this file. 676 * @return int The size of the given file, or zero if it fails. 677 */ 678 private int getFile(String uri, byte[] buffer, int offset, 679 int expectedSize) { 680 int size = 0; 681 try { 682 InputStream stream = mContext.getContentResolver() 683 .openInputStream(Uri.parse(uri)); 684 size = stream.available(); 685 if (size <= expectedSize && buffer != null 686 && buffer.length - offset >= size) { 687 stream.read(buffer, offset, size); 688 } else { 689 size = 0; 690 } 691 stream.close(); 692 } catch (java.io.FileNotFoundException e) { 693 Log.e(LOGTAG, "FileNotFoundException:" + e); 694 size = 0; 695 } catch (java.io.IOException e2) { 696 Log.e(LOGTAG, "IOException: " + e2); 697 size = 0; 698 } 699 return size; 700 } 701 702 /** 703 * Get the InputStream for an Android resource 704 * There are three different kinds of android resources: 705 * - file:///android_res 706 * - file:///android_asset 707 * - content:// 708 * @param url The url to load. 709 * @return An InputStream to the android resource 710 */ 711 private InputStream inputStreamForAndroidResource(String url) { 712 // This list needs to be kept in sync with the list in 713 // external/webkit/WebKit/android/WebCoreSupport/WebUrlLoaderClient.cpp 714 final String ANDROID_ASSET = "file:///android_asset/"; 715 final String ANDROID_RESOURCE = "file:///android_res/"; 716 final String ANDROID_CONTENT = "content:"; 717 718 // file:///android_res 719 if (url.startsWith(ANDROID_RESOURCE)) { 720 url = url.replaceFirst(ANDROID_RESOURCE, ""); 721 if (url == null || url.length() == 0) { 722 Log.e(LOGTAG, "url has length 0 " + url); 723 return null; 724 } 725 int slash = url.indexOf('/'); 726 int dot = url.indexOf('.', slash); 727 if (slash == -1 || dot == -1) { 728 Log.e(LOGTAG, "Incorrect res path: " + url); 729 return null; 730 } 731 String subClassName = url.substring(0, slash); 732 String fieldName = url.substring(slash + 1, dot); 733 String errorMsg = null; 734 try { 735 final Class<?> d = mContext.getApplicationContext() 736 .getClassLoader().loadClass( 737 mContext.getPackageName() + ".R$" 738 + subClassName); 739 final java.lang.reflect.Field field = d.getField(fieldName); 740 final int id = field.getInt(null); 741 TypedValue value = new TypedValue(); 742 mContext.getResources().getValue(id, value, true); 743 if (value.type == TypedValue.TYPE_STRING) { 744 return mContext.getAssets().openNonAsset( 745 value.assetCookie, value.string.toString(), 746 AssetManager.ACCESS_STREAMING); 747 } else { 748 // Old stack only supports TYPE_STRING for res files 749 Log.e(LOGTAG, "not of type string: " + url); 750 return null; 751 } 752 } catch (Exception e) { 753 Log.e(LOGTAG, "Exception: " + url); 754 return null; 755 } 756 757 // file:///android_asset 758 } else if (url.startsWith(ANDROID_ASSET)) { 759 url = url.replaceFirst(ANDROID_ASSET, ""); 760 try { 761 AssetManager assets = mContext.getAssets(); 762 return assets.open(url, AssetManager.ACCESS_STREAMING); 763 } catch (IOException e) { 764 return null; 765 } 766 767 // content:// 768 } else if (mSettings.getAllowContentAccess() && 769 url.startsWith(ANDROID_CONTENT)) { 770 try { 771 // Strip off mimetype, for compatibility with ContentLoader.java 772 // If we don't do this, we can fail to load Gmail attachments, 773 // because the URL being loaded doesn't exactly match the URL we 774 // have permission to read. 775 int mimeIndex = url.lastIndexOf('?'); 776 if (mimeIndex != -1) { 777 url = url.substring(0, mimeIndex); 778 } 779 Uri uri = Uri.parse(url); 780 return mContext.getContentResolver().openInputStream(uri); 781 } catch (Exception e) { 782 Log.e(LOGTAG, "Exception: " + url); 783 return null; 784 } 785 } else { 786 return null; 787 } 788 } 789 790 /** 791 * Start loading a resource. 792 * @param loaderHandle The native ResourceLoader that is the target of the 793 * data. 794 * @param url The url to load. 795 * @param method The http method. 796 * @param headers The http headers. 797 * @param postData If the method is "POST" postData is sent as the request 798 * body. Is null when empty. 799 * @param postDataIdentifier If the post data contained form this is the form identifier, otherwise it is 0. 800 * @param cacheMode The cache mode to use when loading this resource. See WebSettings.setCacheMode 801 * @param mainResource True if the this resource is the main request, not a supporting resource 802 * @param userGesture 803 * @param synchronous True if the load is synchronous. 804 * @return A newly created LoadListener object. 805 */ 806 private LoadListener startLoadingResource(int loaderHandle, 807 String url, 808 String method, 809 HashMap headers, 810 byte[] postData, 811 long postDataIdentifier, 812 int cacheMode, 813 boolean mainResource, 814 boolean userGesture, 815 boolean synchronous, 816 String username, 817 String password) { 818 PerfChecker checker = new PerfChecker(); 819 820 if (mSettings.getCacheMode() != WebSettings.LOAD_DEFAULT) { 821 cacheMode = mSettings.getCacheMode(); 822 } 823 824 if (method.equals("POST")) { 825 // Don't use the cache on POSTs when issuing a normal POST 826 // request. 827 if (cacheMode == WebSettings.LOAD_NORMAL) { 828 cacheMode = WebSettings.LOAD_NO_CACHE; 829 } 830 String[] ret = getUsernamePassword(); 831 if (ret != null) { 832 String domUsername = ret[0]; 833 String domPassword = ret[1]; 834 maybeSavePassword(postData, domUsername, domPassword); 835 } 836 } 837 838 // is this resource the main-frame top-level page? 839 boolean isMainFramePage = mIsMainFrame; 840 841 if (DebugFlags.BROWSER_FRAME) { 842 Log.v(LOGTAG, "startLoadingResource: url=" + url + ", method=" 843 + method + ", postData=" + postData + ", isMainFramePage=" 844 + isMainFramePage + ", mainResource=" + mainResource 845 + ", userGesture=" + userGesture); 846 } 847 848 // Create a LoadListener 849 LoadListener loadListener = LoadListener.getLoadListener(mContext, 850 this, url, loaderHandle, synchronous, isMainFramePage, 851 mainResource, userGesture, postDataIdentifier, username, password); 852 853 if (LoadListener.getNativeLoaderCount() > MAX_OUTSTANDING_REQUESTS) { 854 // send an error message, so that loadListener can be deleted 855 // after this is returned. This is important as LoadListener's 856 // nativeError will remove the request from its DocLoader's request 857 // list. But the set up is not done until this method is returned. 858 loadListener.error( 859 android.net.http.EventHandler.ERROR, mContext.getString( 860 com.android.internal.R.string.httpErrorTooManyRequests)); 861 return loadListener; 862 } 863 864 // Note that we are intentionally skipping 865 // inputStreamForAndroidResource. This is so that FrameLoader will use 866 // the various StreamLoader classes to handle assets. 867 FrameLoader loader = new FrameLoader(loadListener, mSettings, method, 868 mCallbackProxy.shouldInterceptRequest(url)); 869 loader.setHeaders(headers); 870 loader.setPostData(postData); 871 // Set the load mode to the mode used for the current page. 872 // If WebKit wants validation, go to network directly. 873 loader.setCacheMode(headers.containsKey("If-Modified-Since") 874 || headers.containsKey("If-None-Match") ? 875 WebSettings.LOAD_NO_CACHE : cacheMode); 876 // Set referrer to current URL? 877 if (!loader.executeLoad()) { 878 checker.responseAlert("startLoadingResource fail"); 879 } 880 checker.responseAlert("startLoadingResource succeed"); 881 882 return !synchronous ? loadListener : null; 883 } 884 885 /** 886 * If this looks like a POST request (form submission) containing a username 887 * and password, give the user the option of saving them. Will either do 888 * nothing, or block until the UI interaction is complete. 889 * 890 * Called by startLoadingResource when using the Apache HTTP stack. 891 * Called directly by WebKit when using the Chrome HTTP stack. 892 * 893 * @param postData The data about to be sent as the body of a POST request. 894 * @param username The username entered by the user (sniffed from the DOM). 895 * @param password The password entered by the user (sniffed from the DOM). 896 */ 897 private void maybeSavePassword( 898 byte[] postData, String username, String password) { 899 if (postData == null 900 || username == null || username.isEmpty() 901 || password == null || password.isEmpty()) { 902 return; // No password to save. 903 } 904 905 if (!mSettings.getSavePassword()) { 906 return; // User doesn't want to save passwords. 907 } 908 909 try { 910 if (DebugFlags.BROWSER_FRAME) { 911 Assert.assertNotNull(mCallbackProxy.getBackForwardList() 912 .getCurrentItem()); 913 } 914 WebAddress uri = new WebAddress(mCallbackProxy 915 .getBackForwardList().getCurrentItem().getUrl()); 916 String schemePlusHost = uri.getScheme() + uri.getHost(); 917 // Check to see if the username & password appear in 918 // the post data (there could be another form on the 919 // page and that was posted instead. 920 String postString = new String(postData); 921 if (postString.contains(URLEncoder.encode(username)) && 922 postString.contains(URLEncoder.encode(password))) { 923 String[] saved = mDatabase.getUsernamePassword( 924 schemePlusHost); 925 if (saved != null) { 926 // null username implies that user has chosen not to 927 // save password 928 if (saved[0] != null) { 929 // non-null username implies that user has 930 // chosen to save password, so update the 931 // recorded password 932 mDatabase.setUsernamePassword( 933 schemePlusHost, username, password); 934 } 935 } else { 936 // CallbackProxy will handle creating the resume 937 // message 938 mCallbackProxy.onSavePassword(schemePlusHost, username, 939 password, null); 940 } 941 } 942 } catch (ParseException ex) { 943 // if it is bad uri, don't save its password 944 } 945 } 946 947 // Called by jni from the chrome network stack. 948 private WebResourceResponse shouldInterceptRequest(String url) { 949 InputStream androidResource = inputStreamForAndroidResource(url); 950 if (androidResource != null) { 951 return new WebResourceResponse(null, null, androidResource); 952 } 953 WebResourceResponse response = mCallbackProxy.shouldInterceptRequest(url); 954 if (response == null && "browser:incognito".equals(url)) { 955 try { 956 Resources res = mContext.getResources(); 957 InputStream ins = res.openRawResource( 958 com.android.internal.R.raw.incognito_mode_start_page); 959 response = new WebResourceResponse("text/html", "utf8", ins); 960 } catch (NotFoundException ex) { 961 // This shouldn't happen, but try and gracefully handle it jic 962 Log.w(LOGTAG, "Failed opening raw.incognito_mode_start_page", ex); 963 } 964 } 965 return response; 966 } 967 968 /** 969 * Set the progress for the browser activity. Called by native code. 970 * Uses a delay so it does not happen too often. 971 * @param newProgress An int between zero and one hundred representing 972 * the current progress percentage of loading the page. 973 */ 974 private void setProgress(int newProgress) { 975 mCallbackProxy.onProgressChanged(newProgress); 976 if (newProgress == 100) { 977 sendMessageDelayed(obtainMessage(FRAME_COMPLETED), 100); 978 } 979 // FIXME: Need to figure out a better way to switch out of the history 980 // drawing mode. Maybe we can somehow compare the history picture with 981 // the current picture, and switch when it contains more content. 982 if (mFirstLayoutDone && newProgress > TRANSITION_SWITCH_THRESHOLD) { 983 mCallbackProxy.switchOutDrawHistory(); 984 } 985 } 986 987 /** 988 * Send the icon to the activity for display. 989 * @param icon A Bitmap representing a page's favicon. 990 */ 991 private void didReceiveIcon(Bitmap icon) { 992 mCallbackProxy.onReceivedIcon(icon); 993 } 994 995 // Called by JNI when an apple-touch-icon attribute was found. 996 private void didReceiveTouchIconUrl(String url, boolean precomposed) { 997 mCallbackProxy.onReceivedTouchIconUrl(url, precomposed); 998 } 999 1000 /** 1001 * Request a new window from the client. 1002 * @return The BrowserFrame object stored in the new WebView. 1003 */ 1004 private BrowserFrame createWindow(boolean dialog, boolean userGesture) { 1005 return mCallbackProxy.createWindow(dialog, userGesture); 1006 } 1007 1008 /** 1009 * Try to focus this WebView. 1010 */ 1011 private void requestFocus() { 1012 mCallbackProxy.onRequestFocus(); 1013 } 1014 1015 /** 1016 * Close this frame and window. 1017 */ 1018 private void closeWindow(WebViewCore w) { 1019 mCallbackProxy.onCloseWindow(w.getWebView()); 1020 } 1021 1022 // XXX: Must match PolicyAction in FrameLoaderTypes.h in webcore 1023 static final int POLICY_USE = 0; 1024 static final int POLICY_IGNORE = 2; 1025 1026 private void decidePolicyForFormResubmission(int policyFunction) { 1027 Message dontResend = obtainMessage(POLICY_FUNCTION, policyFunction, 1028 POLICY_IGNORE); 1029 Message resend = obtainMessage(POLICY_FUNCTION, policyFunction, 1030 POLICY_USE); 1031 mCallbackProxy.onFormResubmission(dontResend, resend); 1032 } 1033 1034 /** 1035 * Tell the activity to update its global history. 1036 */ 1037 private void updateVisitedHistory(String url, boolean isReload) { 1038 mCallbackProxy.doUpdateVisitedHistory(url, isReload); 1039 } 1040 1041 /** 1042 * Get the CallbackProxy for sending messages to the UI thread. 1043 */ 1044 /* package */ CallbackProxy getCallbackProxy() { 1045 return mCallbackProxy; 1046 } 1047 1048 /** 1049 * Returns the User Agent used by this frame 1050 */ 1051 String getUserAgentString() { 1052 return mSettings.getUserAgentString(); 1053 } 1054 1055 // These ids need to be in sync with enum rawResId in PlatformBridge.h 1056 private static final int NODOMAIN = 1; 1057 private static final int LOADERROR = 2; 1058 /* package */ static final int DRAWABLEDIR = 3; 1059 private static final int FILE_UPLOAD_LABEL = 4; 1060 private static final int RESET_LABEL = 5; 1061 private static final int SUBMIT_LABEL = 6; 1062 private static final int FILE_UPLOAD_NO_FILE_CHOSEN = 7; 1063 1064 private String getRawResFilename(int id) { 1065 return getRawResFilename(id, mContext); 1066 } 1067 /* package */ static String getRawResFilename(int id, Context context) { 1068 int resid; 1069 switch (id) { 1070 case NODOMAIN: 1071 resid = com.android.internal.R.raw.nodomain; 1072 break; 1073 1074 case LOADERROR: 1075 resid = com.android.internal.R.raw.loaderror; 1076 break; 1077 1078 case DRAWABLEDIR: 1079 // use one known resource to find the drawable directory 1080 resid = com.android.internal.R.drawable.btn_check_off; 1081 break; 1082 1083 case FILE_UPLOAD_LABEL: 1084 return context.getResources().getString( 1085 com.android.internal.R.string.upload_file); 1086 1087 case RESET_LABEL: 1088 return context.getResources().getString( 1089 com.android.internal.R.string.reset); 1090 1091 case SUBMIT_LABEL: 1092 return context.getResources().getString( 1093 com.android.internal.R.string.submit); 1094 1095 case FILE_UPLOAD_NO_FILE_CHOSEN: 1096 return context.getResources().getString( 1097 com.android.internal.R.string.no_file_chosen); 1098 1099 default: 1100 Log.e(LOGTAG, "getRawResFilename got incompatible resource ID"); 1101 return ""; 1102 } 1103 TypedValue value = new TypedValue(); 1104 context.getResources().getValue(resid, value, true); 1105 if (id == DRAWABLEDIR) { 1106 String path = value.string.toString(); 1107 int index = path.lastIndexOf('/'); 1108 if (index < 0) { 1109 Log.e(LOGTAG, "Can't find drawable directory."); 1110 return ""; 1111 } 1112 return path.substring(0, index + 1); 1113 } 1114 return value.string.toString(); 1115 } 1116 1117 private float density() { 1118 return mContext.getResources().getDisplayMetrics().density; 1119 } 1120 1121 /** 1122 * Called by JNI when the native HTTP stack gets an authentication request. 1123 * 1124 * We delegate the request to CallbackProxy, and route its response to 1125 * {@link #nativeAuthenticationProceed(int, String, String)} or 1126 * {@link #nativeAuthenticationCancel(int)}. 1127 * 1128 * We don't care what thread the callback is invoked on. All threading is 1129 * handled on the C++ side, because the WebKit thread may be blocked on a 1130 * synchronous call and unable to pump our MessageQueue. 1131 */ 1132 private void didReceiveAuthenticationChallenge( 1133 final int handle, String host, String realm, final boolean useCachedCredentials, 1134 final boolean suppressDialog) { 1135 1136 HttpAuthHandler handler = new HttpAuthHandler() { 1137 1138 @Override 1139 public boolean useHttpAuthUsernamePassword() { 1140 return useCachedCredentials; 1141 } 1142 1143 @Override 1144 public void proceed(String username, String password) { 1145 nativeAuthenticationProceed(handle, username, password); 1146 } 1147 1148 @Override 1149 public void cancel() { 1150 nativeAuthenticationCancel(handle); 1151 } 1152 1153 @Override 1154 public boolean suppressDialog() { 1155 return suppressDialog; 1156 } 1157 }; 1158 mCallbackProxy.onReceivedHttpAuthRequest(handler, host, realm); 1159 } 1160 1161 /** 1162 * Called by JNI when the native HTTPS stack gets an invalid cert chain. 1163 * 1164 * We delegate the request to CallbackProxy, and route its response to 1165 * {@link #nativeSslCertErrorProceed(int)} or 1166 * {@link #nativeSslCertErrorCancel(int, int)}. 1167 */ 1168 private void reportSslCertError( 1169 final int handle, final int cert_error, byte cert_der[], String url) { 1170 final SslError ssl_error; 1171 try { 1172 X509Certificate cert = new X509CertImpl(cert_der); 1173 SslCertificate sslCert = new SslCertificate(cert); 1174 if (JniUtil.useChromiumHttpStack()) { 1175 ssl_error = SslError.SslErrorFromChromiumErrorCode(cert_error, sslCert, 1176 new URL(url).getHost()); 1177 } else { 1178 ssl_error = new SslError(cert_error, cert, url); 1179 } 1180 } catch (IOException e) { 1181 // Can't get the certificate, not much to do. 1182 Log.e(LOGTAG, "Can't get the certificate from WebKit, canceling"); 1183 nativeSslCertErrorCancel(handle, cert_error); 1184 return; 1185 } 1186 1187 SslErrorHandler handler = new SslErrorHandler() { 1188 1189 @Override 1190 public void proceed() { 1191 SslCertLookupTable.getInstance().Allow(ssl_error); 1192 nativeSslCertErrorProceed(handle); 1193 } 1194 1195 @Override 1196 public void cancel() { 1197 SslCertLookupTable.getInstance().Deny(ssl_error); 1198 nativeSslCertErrorCancel(handle, cert_error); 1199 } 1200 }; 1201 1202 if (SslCertLookupTable.getInstance().IsAllowed(ssl_error)) { 1203 nativeSslCertErrorProceed(handle); 1204 } else { 1205 mCallbackProxy.onReceivedSslError(handler, ssl_error); 1206 } 1207 } 1208 1209 /** 1210 * Called by JNI when the native HTTPS stack gets a client 1211 * certificate request. 1212 * 1213 * We delegate the request to CallbackProxy, and route its response to 1214 * {@link #nativeSslClientCert(int, X509Certificate)}. 1215 */ 1216 private void requestClientCert(int handle, byte[] host_and_port_bytes) { 1217 String host_and_port = new String(host_and_port_bytes, Charsets.UTF_8); 1218 SslClientCertLookupTable table = SslClientCertLookupTable.getInstance(); 1219 if (table.IsAllowed(host_and_port)) { 1220 // previously allowed 1221 nativeSslClientCert(handle, 1222 table.PrivateKey(host_and_port), 1223 table.CertificateChain(host_and_port)); 1224 } else if (table.IsDenied(host_and_port)) { 1225 // previously denied 1226 nativeSslClientCert(handle, null, null); 1227 } else { 1228 // previously ignored or new 1229 mCallbackProxy.onReceivedClientCertRequest( 1230 new ClientCertRequestHandler(this, handle, host_and_port, table), 1231 host_and_port); 1232 } 1233 } 1234 1235 /** 1236 * Called by JNI when the native HTTP stack needs to download a file. 1237 * 1238 * We delegate the request to CallbackProxy, which owns the current app's 1239 * DownloadListener. 1240 */ 1241 private void downloadStart(String url, String userAgent, 1242 String contentDisposition, String mimeType, long contentLength) { 1243 // This will only work if the url ends with the filename 1244 if (mimeType.isEmpty()) { 1245 try { 1246 String extension = url.substring(url.lastIndexOf('.') + 1); 1247 mimeType = libcore.net.MimeUtils.guessMimeTypeFromExtension(extension); 1248 // MimeUtils might return null, not sure if downloadmanager is happy with that 1249 if (mimeType == null) 1250 mimeType = ""; 1251 } catch(IndexOutOfBoundsException exception) { 1252 // mimeType string end with a '.', not much to do 1253 } 1254 } 1255 mimeType = MimeTypeMap.getSingleton().remapGenericMimeType( 1256 mimeType, url, contentDisposition); 1257 1258 if (CertTool.getCertType(mimeType) != null) { 1259 mKeyStoreHandler = new KeyStoreHandler(mimeType); 1260 } else { 1261 mCallbackProxy.onDownloadStart(url, userAgent, 1262 contentDisposition, mimeType, contentLength); 1263 } 1264 } 1265 1266 /** 1267 * Called by JNI for Chrome HTTP stack when the Java side needs to access the data. 1268 */ 1269 private void didReceiveData(byte data[], int size) { 1270 if (mKeyStoreHandler != null) mKeyStoreHandler.didReceiveData(data, size); 1271 } 1272 1273 private void didFinishLoading() { 1274 if (mKeyStoreHandler != null) { 1275 mKeyStoreHandler.installCert(mContext); 1276 mKeyStoreHandler = null; 1277 } 1278 } 1279 1280 /** 1281 * Called by JNI when we load a page over SSL. 1282 */ 1283 private void setCertificate(byte cert_der[]) { 1284 try { 1285 X509Certificate cert = new X509CertImpl(cert_der); 1286 mCallbackProxy.onReceivedCertificate(new SslCertificate(cert)); 1287 } catch (IOException e) { 1288 // Can't get the certificate, not much to do. 1289 Log.e(LOGTAG, "Can't get the certificate from WebKit, canceling"); 1290 return; 1291 } 1292 } 1293 1294 /*package*/ SearchBox getSearchBox() { 1295 return mSearchBox; 1296 } 1297 1298 /** 1299 * Called by JNI when processing the X-Auto-Login header. 1300 */ 1301 private void autoLogin(String realm, String account, String args) { 1302 mCallbackProxy.onReceivedLoginRequest(realm, account, args); 1303 } 1304 1305 //========================================================================== 1306 // native functions 1307 //========================================================================== 1308 1309 /** 1310 * Create a new native frame for a given WebView 1311 * @param w A WebView that the frame draws into. 1312 * @param am AssetManager to use to get assets. 1313 * @param list The native side will add and remove items from this list as 1314 * the native list changes. 1315 */ 1316 private native void nativeCreateFrame(WebViewCore w, AssetManager am, 1317 WebBackForwardList list); 1318 1319 /** 1320 * Destroy the native frame. 1321 */ 1322 public native void nativeDestroyFrame(); 1323 1324 private native void nativeCallPolicyFunction(int policyFunction, 1325 int decision); 1326 1327 /** 1328 * Reload the current main frame. 1329 */ 1330 public native void reload(boolean allowStale); 1331 1332 /** 1333 * Go back or forward the number of steps given. 1334 * @param steps A negative or positive number indicating the direction 1335 * and number of steps to move. 1336 */ 1337 private native void nativeGoBackOrForward(int steps); 1338 1339 /** 1340 * stringByEvaluatingJavaScriptFromString will execute the 1341 * JS passed in in the context of this browser frame. 1342 * @param script A javascript string to execute 1343 * 1344 * @return string result of execution or null 1345 */ 1346 public native String stringByEvaluatingJavaScriptFromString(String script); 1347 1348 /** 1349 * Add a javascript interface to the main frame. 1350 */ 1351 private native void nativeAddJavascriptInterface(int nativeFramePointer, 1352 Object obj, String interfaceName); 1353 1354 /** 1355 * Enable or disable the native cache. 1356 */ 1357 /* FIXME: The native cache is always on for now until we have a better 1358 * solution for our 2 caches. */ 1359 private native void setCacheDisabled(boolean disabled); 1360 1361 public native boolean cacheDisabled(); 1362 1363 public native void clearCache(); 1364 1365 /** 1366 * Returns false if the url is bad. 1367 */ 1368 private native void nativeLoadUrl(String url, Map<String, String> headers); 1369 1370 private native void nativePostUrl(String url, byte[] postData); 1371 1372 private native void nativeLoadData(String baseUrl, String data, 1373 String mimeType, String encoding, String historyUrl); 1374 1375 /** 1376 * Stop loading the current page. 1377 */ 1378 public void stopLoading() { 1379 if (mIsMainFrame) { 1380 resetLoadingStates(); 1381 } 1382 nativeStopLoading(); 1383 } 1384 1385 private native void nativeStopLoading(); 1386 1387 /** 1388 * Return true if the document has images. 1389 */ 1390 public native boolean documentHasImages(); 1391 1392 /** 1393 * @return TRUE if there is a password field in the current frame 1394 */ 1395 private native boolean hasPasswordField(); 1396 1397 /** 1398 * Get username and password in the current frame. If found, String[0] is 1399 * username and String[1] is password. Otherwise return NULL. 1400 * @return String[] 1401 */ 1402 private native String[] getUsernamePassword(); 1403 1404 /** 1405 * Set username and password to the proper fields in the current frame 1406 * @param username 1407 * @param password 1408 */ 1409 private native void setUsernamePassword(String username, String password); 1410 1411 private native String nativeSaveWebArchive(String basename, boolean autoname); 1412 1413 private native void nativeOrientationChanged(int orientation); 1414 1415 private native void nativeAuthenticationProceed(int handle, String username, String password); 1416 private native void nativeAuthenticationCancel(int handle); 1417 1418 private native void nativeSslCertErrorProceed(int handle); 1419 private native void nativeSslCertErrorCancel(int handle, int cert_error); 1420 1421 native void nativeSslClientCert(int handle, 1422 byte[] pkcs8EncodedPrivateKey, 1423 byte[][] asn1DerEncodedCertificateChain); 1424} 1425