Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 package org.chromium.net; | 5 package org.chromium.net; |
| 6 | 6 |
| 7 import android.util.Log; | 7 import android.util.Log; |
| 8 | 8 |
| 9 import org.apache.http.conn.ConnectTimeoutException; | 9 import org.apache.http.conn.ConnectTimeoutException; |
| 10 import org.chromium.base.CalledByNative; | 10 import org.chromium.base.CalledByNative; |
| (...skipping 29 matching lines...) Expand all Loading... | |
| 40 private final WritableByteChannel mSink; | 40 private final WritableByteChannel mSink; |
| 41 private Map<String, String> mAdditionalHeaders; | 41 private Map<String, String> mAdditionalHeaders; |
| 42 private String mUploadContentType; | 42 private String mUploadContentType; |
| 43 private String mMethod; | 43 private String mMethod; |
| 44 private byte[] mUploadData; | 44 private byte[] mUploadData; |
| 45 private ReadableByteChannel mUploadChannel; | 45 private ReadableByteChannel mUploadChannel; |
| 46 private boolean mChunkedUpload; | 46 private boolean mChunkedUpload; |
| 47 private IOException mSinkException; | 47 private IOException mSinkException; |
| 48 private volatile boolean mStarted; | 48 private volatile boolean mStarted; |
| 49 private volatile boolean mCanceled; | 49 private volatile boolean mCanceled; |
| 50 private volatile boolean mRecycled; | |
| 51 private volatile boolean mFinished; | 50 private volatile boolean mFinished; |
| 52 private boolean mHeadersAvailable; | 51 private boolean mHeadersAvailable; |
| 53 private String mContentType; | |
| 54 private long mUploadContentLength; | 52 private long mUploadContentLength; |
| 55 private final HttpUrlRequestListener mListener; | 53 private final HttpUrlRequestListener mListener; |
| 56 private boolean mBufferFullResponse; | 54 private boolean mBufferFullResponse; |
| 57 private long mOffset; | 55 private long mOffset; |
| 58 private long mContentLength; | |
| 59 private long mContentLengthLimit; | 56 private long mContentLengthLimit; |
| 60 private boolean mCancelIfContentLengthOverLimit; | 57 private boolean mCancelIfContentLengthOverLimit; |
| 61 private boolean mContentLengthOverLimit; | 58 private boolean mContentLengthOverLimit; |
| 62 private boolean mSkippingToOffset; | 59 private boolean mSkippingToOffset; |
| 63 private long mSize; | 60 private long mSize; |
| 61 | |
| 64 // Indicates whether redirects have been disabled. | 62 // Indicates whether redirects have been disabled. |
| 65 private boolean mDisableRedirects; | 63 private boolean mDisableRedirects; |
| 64 | |
| 65 // Http status code. Default to 0. Populated in onResponseStarted(). | |
| 66 private int mHttpStatusCode = 0; | |
| 67 | |
| 68 // Http status text. Default to null. Populated in onResponseStarted(). | |
| 69 private String mHttpStatusText; | |
| 70 | |
| 71 // Content type. Default to null. Populated in onResponseStarted(). | |
| 72 private String mContentType; | |
| 73 | |
| 74 // Compressed content length as reported by the server. Populated in onRespo nseStarted(). | |
| 75 private long mContentLength; | |
| 76 | |
| 77 // Native error code. Default to no error. Populated in onRequestComplete(). | |
| 78 private int mErrorCode = ChromiumUrlRequestError.SUCCESS; | |
| 79 | |
| 80 // Native error string. Default to null. Populated in onRequestComplete(). | |
| 81 private String mErrorString; | |
| 82 | |
| 83 // Protects access of mUrlRequestAdapter, mStarted, mCanceled, and mFinished . | |
| 66 private final Object mLock = new Object(); | 84 private final Object mLock = new Object(); |
| 67 | 85 |
| 68 public ChromiumUrlRequest(ChromiumUrlRequestContext requestContext, | 86 public ChromiumUrlRequest(ChromiumUrlRequestContext requestContext, |
| 69 String url, int priority, Map<String, String> headers, | 87 String url, int priority, Map<String, String> headers, |
| 70 HttpUrlRequestListener listener) { | 88 HttpUrlRequestListener listener) { |
| 71 this(requestContext, url, priority, headers, | 89 this(requestContext, url, priority, headers, |
| 72 new ChunkedWritableByteChannel(), listener); | 90 new ChunkedWritableByteChannel(), listener); |
| 73 mBufferFullResponse = true; | 91 mBufferFullResponse = true; |
| 74 } | 92 } |
| 75 | 93 |
| (...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 125 } | 143 } |
| 126 | 144 |
| 127 @Override | 145 @Override |
| 128 public void setContentLengthLimit(long limit, boolean cancelEarly) { | 146 public void setContentLengthLimit(long limit, boolean cancelEarly) { |
| 129 mContentLengthLimit = limit; | 147 mContentLengthLimit = limit; |
| 130 mCancelIfContentLengthOverLimit = cancelEarly; | 148 mCancelIfContentLengthOverLimit = cancelEarly; |
| 131 } | 149 } |
| 132 | 150 |
| 133 @Override | 151 @Override |
| 134 public int getHttpStatusCode() { | 152 public int getHttpStatusCode() { |
| 135 int httpStatusCode = nativeGetHttpStatusCode(mUrlRequestAdapter); | |
| 136 | |
| 137 // TODO(mef): Investigate the following: | 153 // TODO(mef): Investigate the following: |
| 138 // If we have been able to successfully resume a previously interrupted | 154 // If we have been able to successfully resume a previously interrupted |
| 139 // download, the status code will be 206, not 200. Since the rest of the | 155 // download, the status code will be 206, not 200. Since the rest of the |
| 140 // application is expecting 200 to indicate success, we need to fake it. | 156 // application is expecting 200 to indicate success, we need to fake it. |
| 141 if (httpStatusCode == 206) { | 157 if (mHttpStatusCode == 206) { |
| 142 httpStatusCode = 200; | 158 return 200; |
| 143 } | 159 } |
| 144 return httpStatusCode; | 160 return mHttpStatusCode; |
| 145 } | 161 } |
| 146 | 162 |
| 147 @Override | 163 @Override |
| 148 public String getHttpStatusText() { | 164 public String getHttpStatusText() { |
| 149 return nativeGetHttpStatusText(mUrlRequestAdapter); | 165 return mHttpStatusText; |
| 150 } | 166 } |
| 151 | 167 |
| 152 /** | 168 /** |
| 153 * Returns an exception if any, or null if the request was completed | 169 * Returns an exception if any, or null if the request has not completed or |
| 154 * successfully. | 170 * completed successfully. |
| 155 */ | 171 */ |
| 156 @Override | 172 @Override |
| 157 public IOException getException() { | 173 public IOException getException() { |
| 158 if (mSinkException != null) { | 174 if (mSinkException != null) { |
| 159 return mSinkException; | 175 return mSinkException; |
| 160 } | 176 } |
| 161 | 177 |
| 162 validateNotRecycled(); | 178 switch (mErrorCode) { |
| 163 | |
| 164 int errorCode = nativeGetErrorCode(mUrlRequestAdapter); | |
| 165 switch (errorCode) { | |
| 166 case ChromiumUrlRequestError.SUCCESS: | 179 case ChromiumUrlRequestError.SUCCESS: |
| 167 if (mContentLengthOverLimit) { | 180 if (mContentLengthOverLimit) { |
| 168 return new ResponseTooLargeException(); | 181 return new ResponseTooLargeException(); |
| 169 } | 182 } |
| 170 return null; | 183 return null; |
| 171 case ChromiumUrlRequestError.UNKNOWN: | 184 case ChromiumUrlRequestError.UNKNOWN: |
| 172 return new IOException( | 185 return new IOException(mErrorString); |
| 173 nativeGetErrorString(mUrlRequestAdapter)); | |
| 174 case ChromiumUrlRequestError.MALFORMED_URL: | 186 case ChromiumUrlRequestError.MALFORMED_URL: |
| 175 return new MalformedURLException("Malformed URL: " + mUrl); | 187 return new MalformedURLException("Malformed URL: " + mUrl); |
| 176 case ChromiumUrlRequestError.CONNECTION_TIMED_OUT: | 188 case ChromiumUrlRequestError.CONNECTION_TIMED_OUT: |
| 177 return new ConnectTimeoutException("Connection timed out"); | 189 return new ConnectTimeoutException("Connection timed out"); |
| 178 case ChromiumUrlRequestError.UNKNOWN_HOST: | 190 case ChromiumUrlRequestError.UNKNOWN_HOST: |
| 179 String host; | 191 String host; |
| 180 try { | 192 try { |
| 181 host = new URL(mUrl).getHost(); | 193 host = new URL(mUrl).getHost(); |
| 182 } catch (MalformedURLException e) { | 194 } catch (MalformedURLException e) { |
| 183 host = mUrl; | 195 host = mUrl; |
| 184 } | 196 } |
| 185 return new UnknownHostException("Unknown host: " + host); | 197 return new UnknownHostException("Unknown host: " + host); |
| 186 case ChromiumUrlRequestError.TOO_MANY_REDIRECTS: | 198 case ChromiumUrlRequestError.TOO_MANY_REDIRECTS: |
| 187 return new IOException("Request failed because there were too " | 199 return new IOException("Request failed because there were too " |
| 188 + "many redirects or redirects have been disabled"); | 200 + "many redirects or redirects have been disabled"); |
| 189 default: | 201 default: |
| 190 throw new IllegalStateException( | 202 throw new IllegalStateException( |
| 191 "Unrecognized error code: " + errorCode); | 203 "Unrecognized error code: " + mErrorCode); |
| 192 } | 204 } |
| 193 } | 205 } |
| 194 | 206 |
| 195 @Override | 207 @Override |
| 196 public ByteBuffer getByteBuffer() { | 208 public ByteBuffer getByteBuffer() { |
| 197 return ((ChunkedWritableByteChannel) getSink()).getByteBuffer(); | 209 return ((ChunkedWritableByteChannel) getSink()).getByteBuffer(); |
| 198 } | 210 } |
| 199 | 211 |
| 200 @Override | 212 @Override |
| 201 public byte[] getResponseAsBytes() { | 213 public byte[] getResponseAsBytes() { |
| (...skipping 125 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 327 } | 339 } |
| 328 | 340 |
| 329 @Override | 341 @Override |
| 330 public void start() { | 342 public void start() { |
| 331 synchronized (mLock) { | 343 synchronized (mLock) { |
| 332 if (mCanceled) { | 344 if (mCanceled) { |
| 333 return; | 345 return; |
| 334 } | 346 } |
| 335 | 347 |
| 336 validateNotStarted(); | 348 validateNotStarted(); |
| 337 validateNotRecycled(); | 349 validateNativeAdapterNotDestroyed(); |
| 338 | 350 |
| 339 mStarted = true; | 351 mStarted = true; |
| 340 | 352 |
| 341 if (mHeaders != null && !mHeaders.isEmpty()) { | 353 if (mHeaders != null && !mHeaders.isEmpty()) { |
| 342 for (Entry<String, String> entry : mHeaders.entrySet()) { | 354 for (Entry<String, String> entry : mHeaders.entrySet()) { |
| 343 nativeAddHeader(mUrlRequestAdapter, entry.getKey(), | 355 nativeAddHeader(mUrlRequestAdapter, entry.getKey(), |
| 344 entry.getValue()); | 356 entry.getValue()); |
| 345 } | 357 } |
| 346 } | 358 } |
| 347 | 359 |
| (...skipping 29 matching lines...) Expand all Loading... | |
| 377 | 389 |
| 378 @Override | 390 @Override |
| 379 public void cancel() { | 391 public void cancel() { |
| 380 synchronized (mLock) { | 392 synchronized (mLock) { |
| 381 if (mCanceled) { | 393 if (mCanceled) { |
| 382 return; | 394 return; |
| 383 } | 395 } |
| 384 | 396 |
| 385 mCanceled = true; | 397 mCanceled = true; |
| 386 | 398 |
| 387 if (!mRecycled) { | 399 if (mUrlRequestAdapter != 0) { |
| 388 nativeCancel(mUrlRequestAdapter); | 400 nativeCancel(mUrlRequestAdapter); |
| 389 } | 401 } |
| 390 } | 402 } |
| 391 } | 403 } |
| 392 | 404 |
| 393 @Override | 405 @Override |
| 394 public boolean isCanceled() { | 406 public boolean isCanceled() { |
| 395 synchronized (mLock) { | 407 synchronized (mLock) { |
| 396 return mCanceled; | 408 return mCanceled; |
| 397 } | 409 } |
| 398 } | 410 } |
| 399 | 411 |
| 400 public boolean isRecycled() { | |
| 401 synchronized (mLock) { | |
| 402 return mRecycled; | |
| 403 } | |
| 404 } | |
| 405 | |
| 406 @Override | 412 @Override |
| 407 public String getNegotiatedProtocol() { | 413 public String getNegotiatedProtocol() { |
| 408 validateNotRecycled(); | 414 synchronized (mLock) { |
|
xunjieli
2015/03/03 22:53:12
I realize there can be a race when we call get* me
| |
| 409 validateHeadersAvailable(); | 415 validateNativeAdapterNotDestroyed(); |
| 410 return nativeGetNegotiatedProtocol(mUrlRequestAdapter); | 416 validateHeadersAvailable(); |
| 417 return nativeGetNegotiatedProtocol(mUrlRequestAdapter); | |
| 418 } | |
| 411 } | 419 } |
| 412 | 420 |
| 413 @Override | 421 @Override |
| 414 public String getContentType() { | 422 public String getContentType() { |
| 415 return mContentType; | 423 return mContentType; |
| 416 } | 424 } |
| 417 | 425 |
| 418 @Override | 426 @Override |
| 419 public String getHeader(String name) { | 427 public String getHeader(String name) { |
| 420 validateNotRecycled(); | 428 synchronized (mLock) { |
| 421 validateHeadersAvailable(); | 429 validateNativeAdapterNotDestroyed(); |
| 422 return nativeGetHeader(mUrlRequestAdapter, name); | 430 validateHeadersAvailable(); |
| 431 return nativeGetHeader(mUrlRequestAdapter, name); | |
| 432 } | |
| 423 } | 433 } |
| 424 | 434 |
| 425 // All response headers. | 435 // All response headers. |
| 426 @Override | 436 @Override |
| 427 public Map<String, List<String>> getAllHeaders() { | 437 public Map<String, List<String>> getAllHeaders() { |
| 428 validateNotRecycled(); | 438 synchronized (mLock) { |
| 429 validateHeadersAvailable(); | 439 validateNativeAdapterNotDestroyed(); |
| 430 ResponseHeadersMap result = new ResponseHeadersMap(); | 440 validateHeadersAvailable(); |
| 431 nativeGetAllHeaders(mUrlRequestAdapter, result); | 441 ResponseHeadersMap result = new ResponseHeadersMap(); |
| 432 return result; | 442 nativeGetAllHeaders(mUrlRequestAdapter, result); |
| 443 return result; | |
| 444 } | |
| 433 } | 445 } |
| 434 | 446 |
| 435 @Override | 447 @Override |
| 436 public String getUrl() { | 448 public String getUrl() { |
| 437 return mUrl; | 449 return mUrl; |
| 438 } | 450 } |
| 439 | 451 |
| 440 private static int convertRequestPriority(int priority) { | 452 private static int convertRequestPriority(int priority) { |
| 441 switch (priority) { | 453 switch (priority) { |
| 442 case HttpUrlRequest.REQUEST_PRIORITY_IDLE: | 454 case HttpUrlRequest.REQUEST_PRIORITY_IDLE: |
| (...skipping 13 matching lines...) Expand all Loading... | |
| 456 | 468 |
| 457 private void onContentLengthOverLimit() { | 469 private void onContentLengthOverLimit() { |
| 458 mContentLengthOverLimit = true; | 470 mContentLengthOverLimit = true; |
| 459 cancel(); | 471 cancel(); |
| 460 } | 472 } |
| 461 | 473 |
| 462 /** | 474 /** |
| 463 * A callback invoked when the response has been fully consumed. | 475 * A callback invoked when the response has been fully consumed. |
| 464 */ | 476 */ |
| 465 private void onRequestComplete() { | 477 private void onRequestComplete() { |
| 478 mErrorCode = nativeGetErrorCode(mUrlRequestAdapter); | |
| 479 mErrorString = nativeGetErrorString(mUrlRequestAdapter); | |
| 480 // When there is an error or redirects have been disabled, | |
| 481 // onResponseStarted is often not invoked. | |
| 482 // Populate status code and status text if that's the case. | |
| 483 // Note that besides redirects, these two fields may be set on the | |
| 484 // request for AUTH and CERT requests. | |
| 485 if (mErrorCode != ChromiumUrlRequestError.SUCCESS) { | |
| 486 mHttpStatusCode = nativeGetHttpStatusCode(mUrlRequestAdapter); | |
| 487 mHttpStatusText = nativeGetHttpStatusText(mUrlRequestAdapter); | |
| 488 } | |
| 466 mListener.onRequestComplete(this); | 489 mListener.onRequestComplete(this); |
| 467 } | 490 } |
| 468 | 491 |
| 469 private void validateNotRecycled() { | 492 private void validateNativeAdapterNotDestroyed() { |
| 470 if (mRecycled) { | 493 if (mUrlRequestAdapter == 0) { |
| 471 throw new IllegalStateException("Accessing recycled request"); | 494 throw new IllegalStateException("Adapter has been destroyed"); |
| 472 } | 495 } |
| 473 } | 496 } |
| 474 | 497 |
| 475 private void validateNotStarted() { | 498 private void validateNotStarted() { |
| 476 if (mStarted) { | 499 if (mStarted) { |
| 477 throw new IllegalStateException("Request already started"); | 500 throw new IllegalStateException("Request already started"); |
| 478 } | 501 } |
| 479 } | 502 } |
| 480 | 503 |
| 481 private void validateHeadersAvailable() { | 504 private void validateHeadersAvailable() { |
| (...skipping 26 matching lines...) Expand all Loading... | |
| 508 "Exception trying to cancel request", cancel_exception); | 531 "Exception trying to cancel request", cancel_exception); |
| 509 } | 532 } |
| 510 } | 533 } |
| 511 | 534 |
| 512 /** | 535 /** |
| 513 * A callback invoked when the first chunk of the response has arrived. | 536 * A callback invoked when the first chunk of the response has arrived. |
| 514 */ | 537 */ |
| 515 @CalledByNative | 538 @CalledByNative |
| 516 private void onResponseStarted() { | 539 private void onResponseStarted() { |
| 517 try { | 540 try { |
| 541 mHttpStatusCode = nativeGetHttpStatusCode(mUrlRequestAdapter); | |
| 542 mHttpStatusText = nativeGetHttpStatusText(mUrlRequestAdapter); | |
| 518 mContentType = nativeGetContentType(mUrlRequestAdapter); | 543 mContentType = nativeGetContentType(mUrlRequestAdapter); |
| 519 mContentLength = nativeGetContentLength(mUrlRequestAdapter); | 544 mContentLength = nativeGetContentLength(mUrlRequestAdapter); |
| 520 mHeadersAvailable = true; | 545 mHeadersAvailable = true; |
| 521 | 546 |
| 522 if (mContentLengthLimit > 0 | 547 if (mContentLengthLimit > 0 |
| 523 && mContentLength > mContentLengthLimit | 548 && mContentLength > mContentLengthLimit |
| 524 && mCancelIfContentLengthOverLimit) { | 549 && mCancelIfContentLengthOverLimit) { |
| 525 onContentLengthOverLimit(); | 550 onContentLengthOverLimit(); |
| 526 return; | 551 return; |
| 527 } | 552 } |
| 528 | 553 |
| 529 if (mBufferFullResponse && mContentLength != -1 | 554 if (mBufferFullResponse && mContentLength != -1 |
| 530 && !mContentLengthOverLimit) { | 555 && !mContentLengthOverLimit) { |
| 531 ((ChunkedWritableByteChannel) getSink()).setCapacity( | 556 ((ChunkedWritableByteChannel) getSink()).setCapacity( |
| 532 (int) mContentLength); | 557 (int) mContentLength); |
| 533 } | 558 } |
| 534 | 559 |
| 535 if (mOffset != 0) { | 560 if (mOffset != 0) { |
| 536 // The server may ignore the request for a byte range, in which | 561 // The server may ignore the request for a byte range, in which |
| 537 // case status code will be 200, instead of 206. Note that we | 562 // case status code will be 200, instead of 206. Note that we |
| 538 // cannot call getHttpStatusCode as it rewrites 206 into 200. | 563 // cannot call getHttpStatusCode as it rewrites 206 into 200. |
| 539 if (nativeGetHttpStatusCode(mUrlRequestAdapter) == 200) { | 564 if (mHttpStatusCode == 200) { |
| 540 // TODO(mef): Revisit this logic. | 565 // TODO(mef): Revisit this logic. |
| 541 if (mContentLength != -1) { | 566 if (mContentLength != -1) { |
| 542 mContentLength -= mOffset; | 567 mContentLength -= mOffset; |
| 543 } | 568 } |
| 544 mSkippingToOffset = true; | 569 mSkippingToOffset = true; |
| 545 } else { | 570 } else { |
| 546 mSize = mOffset; | 571 mSize = mOffset; |
| 547 } | 572 } |
| 548 } | 573 } |
| 549 mListener.onResponseStarted(this); | 574 mListener.onResponseStarted(this); |
| (...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 600 @SuppressWarnings("unused") | 625 @SuppressWarnings("unused") |
| 601 @CalledByNative | 626 @CalledByNative |
| 602 private void finish() { | 627 private void finish() { |
| 603 try { | 628 try { |
| 604 synchronized (mLock) { | 629 synchronized (mLock) { |
| 605 if (mDisableRedirects) { | 630 if (mDisableRedirects) { |
| 606 mHeadersAvailable = true; | 631 mHeadersAvailable = true; |
| 607 } | 632 } |
| 608 mFinished = true; | 633 mFinished = true; |
| 609 | 634 |
| 610 if (mRecycled) { | 635 if (mUrlRequestAdapter == 0) { |
| 611 return; | 636 return; |
| 612 } | 637 } |
| 613 try { | 638 try { |
| 614 mSink.close(); | 639 mSink.close(); |
| 615 } catch (IOException e) { | 640 } catch (IOException e) { |
| 616 // Ignore | 641 // Ignore |
| 617 } | 642 } |
| 618 try { | 643 try { |
| 619 if (mUploadChannel != null && mUploadChannel.isOpen()) { | 644 if (mUploadChannel != null && mUploadChannel.isOpen()) { |
| 620 mUploadChannel.close(); | 645 mUploadChannel.close(); |
| 621 } | 646 } |
| 622 } catch (IOException e) { | 647 } catch (IOException e) { |
| 623 // Ignore | 648 // Ignore |
| 624 } | 649 } |
| 625 onRequestComplete(); | 650 onRequestComplete(); |
| 626 nativeDestroyRequestAdapter(mUrlRequestAdapter); | 651 nativeDestroyRequestAdapter(mUrlRequestAdapter); |
| 627 mUrlRequestAdapter = 0; | 652 mUrlRequestAdapter = 0; |
| 628 mRecycled = true; | |
| 629 } | 653 } |
| 630 } catch (Exception e) { | 654 } catch (Exception e) { |
| 631 mSinkException = new IOException("Exception in finish", e); | 655 mSinkException = new IOException("Exception in finish", e); |
| 632 } | 656 } |
| 633 } | 657 } |
| 634 | 658 |
| 635 /** | 659 /** |
| 636 * Appends header |name| with value |value| to |headersMap|. | 660 * Appends header |name| with value |value| to |headersMap|. |
| 637 */ | 661 */ |
| 638 @SuppressWarnings("unused") | 662 @SuppressWarnings("unused") |
| (...skipping 81 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 720 private native void nativeGetAllHeaders(long urlRequestAdapter, | 744 private native void nativeGetAllHeaders(long urlRequestAdapter, |
| 721 ResponseHeadersMap headers); | 745 ResponseHeadersMap headers); |
| 722 | 746 |
| 723 private native String nativeGetNegotiatedProtocol(long urlRequestAdapter); | 747 private native String nativeGetNegotiatedProtocol(long urlRequestAdapter); |
| 724 | 748 |
| 725 // Explicit class to work around JNI-generator generics confusion. | 749 // Explicit class to work around JNI-generator generics confusion. |
| 726 private static class ResponseHeadersMap extends | 750 private static class ResponseHeadersMap extends |
| 727 HashMap<String, List<String>> { | 751 HashMap<String, List<String>> { |
| 728 } | 752 } |
| 729 } | 753 } |
| OLD | NEW |