Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(267)

Side by Side Diff: components/cronet/android/java/src/org/chromium/net/ChromiumUrlRequest.java

Issue 1849753002: [Cronet] Separate Cronet implementation and API by package name. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: sync Created 4 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
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
3 // found in the LICENSE file.
4
5 package org.chromium.net;
6
7 import android.util.Log;
8
9 import org.chromium.base.annotations.CalledByNative;
10 import org.chromium.base.annotations.JNINamespace;
11 import org.chromium.base.annotations.SuppressFBWarnings;
12
13 import java.io.IOException;
14 import java.net.MalformedURLException;
15 import java.net.SocketTimeoutException;
16 import java.net.URL;
17 import java.net.UnknownHostException;
18 import java.nio.ByteBuffer;
19 import java.nio.channels.ReadableByteChannel;
20 import java.nio.channels.WritableByteChannel;
21 import java.util.ArrayList;
22 import java.util.HashMap;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Map.Entry;
26
27 /**
28 * Network request using the native http stack implementation.
29 * @deprecated Use {@link CronetUrlRequest} instead.
30 */
31 @JNINamespace("cronet")
32 @Deprecated
33 public class ChromiumUrlRequest implements HttpUrlRequest {
34 /**
35 * Native adapter object, owned by UrlRequest.
36 */
37 private long mUrlRequestAdapter;
38 private final ChromiumUrlRequestContext mRequestContext;
39 private final String mUrl;
40 private final int mPriority;
41 private final Map<String, String> mHeaders;
42 private final WritableByteChannel mSink;
43 private Map<String, String> mAdditionalHeaders;
44 private String mUploadContentType;
45 private String mMethod;
46 private byte[] mUploadData;
47 private ReadableByteChannel mUploadChannel;
48 private boolean mChunkedUpload;
49 private IOException mSinkException;
50 private volatile boolean mStarted;
51 private volatile boolean mCanceled;
52 private volatile boolean mFinished;
53 private boolean mHeadersAvailable;
54 private long mUploadContentLength;
55 private final HttpUrlRequestListener mListener;
56 private boolean mBufferFullResponse;
57 private long mOffset;
58 private long mContentLengthLimit;
59 private boolean mCancelIfContentLengthOverLimit;
60 private boolean mContentLengthOverLimit;
61 private boolean mSkippingToOffset;
62 private long mSize;
63
64 // Indicates whether redirects have been disabled.
65 private boolean mDisableRedirects;
66
67 // Http status code. Default to 0. Populated in onResponseStarted().
68 private int mHttpStatusCode = 0;
69
70 // Http status text. Default to null. Populated in onResponseStarted().
71 private String mHttpStatusText;
72
73 // Content type. Default to null. Populated in onResponseStarted().
74 private String mContentType;
75
76 // Compressed content length as reported by the server. Populated in onRespo nseStarted().
77 private long mContentLength;
78
79 // Native error code. Default to no error. Populated in onRequestComplete().
80 private int mErrorCode = ChromiumUrlRequestError.SUCCESS;
81
82 // Native error string. Default to null. Populated in onRequestComplete().
83 private String mErrorString;
84
85 // Protects access of mUrlRequestAdapter, mStarted, mCanceled, and mFinished .
86 private final Object mLock = new Object();
87
88 public ChromiumUrlRequest(ChromiumUrlRequestContext requestContext,
89 String url, int priority, Map<String, String> headers,
90 HttpUrlRequestListener listener) {
91 this(requestContext, url, priority, headers,
92 new ChunkedWritableByteChannel(), listener);
93 mBufferFullResponse = true;
94 }
95
96 /**
97 * Constructor.
98 *
99 * @param requestContext The context.
100 * @param url The URL.
101 * @param priority Request priority, e.g. {@link #REQUEST_PRIORITY_MEDIUM}.
102 * @param headers HTTP headers.
103 * @param sink The output channel into which downloaded content will be
104 * written.
105 */
106 public ChromiumUrlRequest(ChromiumUrlRequestContext requestContext,
107 String url, int priority, Map<String, String> headers,
108 WritableByteChannel sink, HttpUrlRequestListener listener) {
109 if (requestContext == null) {
110 throw new NullPointerException("Context is required");
111 }
112 if (url == null) {
113 throw new NullPointerException("URL is required");
114 }
115 mRequestContext = requestContext;
116 mUrl = url;
117 mPriority = convertRequestPriority(priority);
118 mHeaders = headers;
119 mSink = sink;
120 mUrlRequestAdapter = nativeCreateRequestAdapter(
121 mRequestContext.getUrlRequestContextAdapter(), mUrl, mPriority);
122 mListener = listener;
123 }
124
125 @Override
126 public void setOffset(long offset) {
127 mOffset = offset;
128 if (offset != 0) {
129 addHeader("Range", "bytes=" + offset + "-");
130 }
131 }
132
133 /**
134 * The compressed content length as reported by the server. May be -1 if
135 * the server did not provide a length. Some servers may also report the
136 * wrong number. Since this is the compressed content length, and only
137 * uncompressed content is returned by the consumer, the consumer should
138 * not rely on this value.
139 */
140 @Override
141 public long getContentLength() {
142 return mContentLength;
143 }
144
145 @Override
146 public void setContentLengthLimit(long limit, boolean cancelEarly) {
147 mContentLengthLimit = limit;
148 mCancelIfContentLengthOverLimit = cancelEarly;
149 }
150
151 @Override
152 public int getHttpStatusCode() {
153 // TODO(mef): Investigate the following:
154 // If we have been able to successfully resume a previously interrupted
155 // download, the status code will be 206, not 200. Since the rest of the
156 // application is expecting 200 to indicate success, we need to fake it.
157 if (mHttpStatusCode == 206) {
158 return 200;
159 }
160 return mHttpStatusCode;
161 }
162
163 @Override
164 public String getHttpStatusText() {
165 return mHttpStatusText;
166 }
167
168 /**
169 * Returns an exception if any, or null if the request has not completed or
170 * completed successfully.
171 */
172 @Override
173 public IOException getException() {
174 if (mSinkException != null) {
175 return mSinkException;
176 }
177
178 switch (mErrorCode) {
179 case ChromiumUrlRequestError.SUCCESS:
180 if (mContentLengthOverLimit) {
181 return new ResponseTooLargeException();
182 }
183 return null;
184 case ChromiumUrlRequestError.UNKNOWN:
185 return new IOException(mErrorString);
186 case ChromiumUrlRequestError.MALFORMED_URL:
187 return new MalformedURLException("Malformed URL: " + mUrl);
188 case ChromiumUrlRequestError.CONNECTION_TIMED_OUT:
189 return new SocketTimeoutException("Connection timed out");
190 case ChromiumUrlRequestError.UNKNOWN_HOST:
191 String host;
192 try {
193 host = new URL(mUrl).getHost();
194 } catch (MalformedURLException e) {
195 host = mUrl;
196 }
197 return new UnknownHostException("Unknown host: " + host);
198 case ChromiumUrlRequestError.TOO_MANY_REDIRECTS:
199 return new IOException("Request failed because there were too "
200 + "many redirects or redirects have been disabled");
201 default:
202 throw new IllegalStateException(
203 "Unrecognized error code: " + mErrorCode);
204 }
205 }
206
207 @Override
208 public ByteBuffer getByteBuffer() {
209 return ((ChunkedWritableByteChannel) getSink()).getByteBuffer();
210 }
211
212 @Override
213 public byte[] getResponseAsBytes() {
214 return ((ChunkedWritableByteChannel) getSink()).getBytes();
215 }
216
217 /**
218 * Adds a request header. Must be done before request has started.
219 */
220 public void addHeader(String header, String value) {
221 synchronized (mLock) {
222 validateNotStarted();
223 if (mAdditionalHeaders == null) {
224 mAdditionalHeaders = new HashMap<String, String>();
225 }
226 mAdditionalHeaders.put(header, value);
227 }
228 }
229
230 /**
231 * Sets data to upload as part of a POST or PUT request.
232 *
233 * @param contentType MIME type of the upload content or null if this is not
234 * an upload.
235 * @param data The content that needs to be uploaded.
236 */
237 @Override
238 @SuppressFBWarnings("EI_EXPOSE_REP2")
239 public void setUploadData(String contentType, byte[] data) {
240 synchronized (mLock) {
241 validateNotStarted();
242 validateContentType(contentType);
243 mUploadContentType = contentType;
244 mUploadData = data;
245 mUploadChannel = null;
246 mChunkedUpload = false;
247 }
248 }
249
250 /**
251 * Sets a readable byte channel to upload as part of a POST or PUT request.
252 *
253 * @param contentType MIME type of the upload content or null if this is not
254 * an upload request.
255 * @param channel The channel to read to read upload data from if this is an
256 * upload request.
257 * @param contentLength The length of data to upload.
258 */
259 @Override
260 public void setUploadChannel(String contentType,
261 ReadableByteChannel channel, long contentLength) {
262 synchronized (mLock) {
263 validateNotStarted();
264 validateContentType(contentType);
265 mUploadContentType = contentType;
266 mUploadChannel = channel;
267 mUploadContentLength = contentLength;
268 mUploadData = null;
269 mChunkedUpload = false;
270 }
271 }
272
273 /**
274 * Sets this request up for chunked uploading. To upload data call
275 * {@link #appendChunk(ByteBuffer, boolean)} after {@link #start()}.
276 *
277 * @param contentType MIME type of the post content or null if this is not a
278 * POST request.
279 */
280 public void setChunkedUpload(String contentType) {
281 synchronized (mLock) {
282 validateNotStarted();
283 validateContentType(contentType);
284 mUploadContentType = contentType;
285 mChunkedUpload = true;
286 mUploadData = null;
287 mUploadChannel = null;
288 }
289 }
290
291 /**
292 * Uploads a new chunk. Must have called {@link #setChunkedUpload(String)}
293 * and {@link #start()}.
294 *
295 * @param chunk The data, which will not be modified. Its current position.
296 * must be zero. The last chunk can be empty, but all other
297 * chunks must be non-empty.
298 * @param isLastChunk Whether this chunk is the last one.
299 */
300 public void appendChunk(ByteBuffer chunk, boolean isLastChunk)
301 throws IOException {
302 if (!isLastChunk && !chunk.hasRemaining()) {
303 throw new IllegalArgumentException(
304 "Attempted to write empty buffer.");
305 }
306 if (chunk.position() != 0) {
307 throw new IllegalArgumentException("The position must be zero.");
308 }
309 synchronized (mLock) {
310 if (!mStarted) {
311 throw new IllegalStateException("Request not yet started.");
312 }
313 if (!mChunkedUpload) {
314 throw new IllegalStateException(
315 "Request not set for chunked uploadind.");
316 }
317 if (mUrlRequestAdapter == 0) {
318 throw new IOException("Native peer destroyed.");
319 }
320 nativeAppendChunk(mUrlRequestAdapter, chunk, chunk.limit(),
321 isLastChunk);
322 }
323 }
324
325 @Override
326 public void setHttpMethod(String method) {
327 validateNotStarted();
328 mMethod = method;
329 }
330
331 @Override
332 public void disableRedirects() {
333 synchronized (mLock) {
334 validateNotStarted();
335 validateNativeAdapterNotDestroyed();
336 mDisableRedirects = true;
337 nativeDisableRedirects(mUrlRequestAdapter);
338 }
339 }
340
341 public WritableByteChannel getSink() {
342 return mSink;
343 }
344
345 @Override
346 public void start() {
347 synchronized (mLock) {
348 if (mCanceled) {
349 return;
350 }
351
352 validateNotStarted();
353 validateNativeAdapterNotDestroyed();
354
355 mStarted = true;
356
357 if (mHeaders != null && !mHeaders.isEmpty()) {
358 for (Entry<String, String> entry : mHeaders.entrySet()) {
359 nativeAddHeader(mUrlRequestAdapter, entry.getKey(),
360 entry.getValue());
361 }
362 }
363
364 if (mAdditionalHeaders != null) {
365 for (Entry<String, String> entry :
366 mAdditionalHeaders.entrySet()) {
367 nativeAddHeader(mUrlRequestAdapter, entry.getKey(),
368 entry.getValue());
369 }
370 }
371
372 if (mUploadData != null && mUploadData.length > 0) {
373 nativeSetUploadData(mUrlRequestAdapter, mUploadContentType,
374 mUploadData);
375 } else if (mUploadChannel != null) {
376 nativeSetUploadChannel(mUrlRequestAdapter, mUploadContentType,
377 mUploadContentLength);
378 } else if (mChunkedUpload) {
379 nativeEnableChunkedUpload(mUrlRequestAdapter,
380 mUploadContentType);
381 }
382
383 // Note: The above functions to set the upload body also set the
384 // method to POST, behind the scenes, so if mMethod is null but
385 // there's an upload body, the method will default to POST.
386 if (mMethod != null) {
387 nativeSetMethod(mUrlRequestAdapter, mMethod);
388 }
389
390 nativeStart(mUrlRequestAdapter);
391 }
392 }
393
394 @Override
395 public void cancel() {
396 synchronized (mLock) {
397 if (mCanceled) {
398 return;
399 }
400
401 mCanceled = true;
402
403 if (mUrlRequestAdapter != 0) {
404 nativeCancel(mUrlRequestAdapter);
405 }
406 }
407 }
408
409 @Override
410 public boolean isCanceled() {
411 synchronized (mLock) {
412 return mCanceled;
413 }
414 }
415
416 @Override
417 public String getNegotiatedProtocol() {
418 synchronized (mLock) {
419 validateNativeAdapterNotDestroyed();
420 validateHeadersAvailable();
421 return nativeGetNegotiatedProtocol(mUrlRequestAdapter);
422 }
423 }
424
425 @Override
426 public boolean wasCached() {
427 synchronized (mLock) {
428 validateNativeAdapterNotDestroyed();
429 validateHeadersAvailable();
430 return nativeGetWasCached(mUrlRequestAdapter);
431 }
432 }
433
434 @Override
435 public String getContentType() {
436 return mContentType;
437 }
438
439 @Override
440 public String getHeader(String name) {
441 synchronized (mLock) {
442 validateNativeAdapterNotDestroyed();
443 validateHeadersAvailable();
444 return nativeGetHeader(mUrlRequestAdapter, name);
445 }
446 }
447
448 // All response headers.
449 @Override
450 public Map<String, List<String>> getAllHeaders() {
451 synchronized (mLock) {
452 validateNativeAdapterNotDestroyed();
453 validateHeadersAvailable();
454 ResponseHeadersMap result = new ResponseHeadersMap();
455 nativeGetAllHeaders(mUrlRequestAdapter, result);
456 return result;
457 }
458 }
459
460 @Override
461 public String getUrl() {
462 return mUrl;
463 }
464
465 private static int convertRequestPriority(int priority) {
466 switch (priority) {
467 case REQUEST_PRIORITY_IDLE:
468 return ChromiumUrlRequestPriority.IDLE;
469 case REQUEST_PRIORITY_LOWEST:
470 return ChromiumUrlRequestPriority.LOWEST;
471 case REQUEST_PRIORITY_LOW:
472 return ChromiumUrlRequestPriority.LOW;
473 case REQUEST_PRIORITY_MEDIUM:
474 return ChromiumUrlRequestPriority.MEDIUM;
475 case REQUEST_PRIORITY_HIGHEST:
476 return ChromiumUrlRequestPriority.HIGHEST;
477 default:
478 return ChromiumUrlRequestPriority.MEDIUM;
479 }
480 }
481
482 private void onContentLengthOverLimit() {
483 mContentLengthOverLimit = true;
484 cancel();
485 }
486
487 /**
488 * A callback invoked when the response has been fully consumed.
489 */
490 private void onRequestComplete() {
491 mErrorCode = nativeGetErrorCode(mUrlRequestAdapter);
492 mErrorString = nativeGetErrorString(mUrlRequestAdapter);
493 // When there is an error or redirects have been disabled,
494 // onResponseStarted is often not invoked.
495 // Populate status code and status text if that's the case.
496 // Note that besides redirects, these two fields may be set on the
497 // request for AUTH and CERT requests.
498 if (mErrorCode != ChromiumUrlRequestError.SUCCESS) {
499 mHttpStatusCode = nativeGetHttpStatusCode(mUrlRequestAdapter);
500 mHttpStatusText = nativeGetHttpStatusText(mUrlRequestAdapter);
501 }
502 mListener.onRequestComplete(this);
503 }
504
505 private void validateNativeAdapterNotDestroyed() {
506 if (mUrlRequestAdapter == 0) {
507 throw new IllegalStateException("Adapter has been destroyed");
508 }
509 }
510
511 private void validateNotStarted() {
512 if (mStarted) {
513 throw new IllegalStateException("Request already started");
514 }
515 }
516
517 private void validateHeadersAvailable() {
518 if (!mHeadersAvailable) {
519 throw new IllegalStateException("Response headers not available");
520 }
521 }
522
523 private void validateContentType(String contentType) {
524 if (contentType == null) {
525 throw new NullPointerException("contentType is required");
526 }
527 }
528
529 // Private methods called by native library.
530
531 /**
532 * If @CalledByNative method throws an exception, request gets canceled
533 * and exception could be retrieved from request using getException().
534 */
535 private void onCalledByNativeException(Exception e) {
536 mSinkException = new IOException(
537 "CalledByNative method has thrown an exception", e);
538 Log.e(ChromiumUrlRequestContext.LOG_TAG,
539 "Exception in CalledByNative method", e);
540 try {
541 cancel();
542 } catch (Exception cancel_exception) {
543 Log.e(ChromiumUrlRequestContext.LOG_TAG,
544 "Exception trying to cancel request", cancel_exception);
545 }
546 }
547
548 /**
549 * A callback invoked when the first chunk of the response has arrived.
550 */
551 @CalledByNative
552 private void onResponseStarted() {
553 try {
554 mHttpStatusCode = nativeGetHttpStatusCode(mUrlRequestAdapter);
555 mHttpStatusText = nativeGetHttpStatusText(mUrlRequestAdapter);
556 mContentType = nativeGetContentType(mUrlRequestAdapter);
557 mContentLength = nativeGetContentLength(mUrlRequestAdapter);
558 mHeadersAvailable = true;
559
560 if (mContentLengthLimit > 0
561 && mContentLength > mContentLengthLimit
562 && mCancelIfContentLengthOverLimit) {
563 onContentLengthOverLimit();
564 return;
565 }
566
567 if (mBufferFullResponse && mContentLength != -1
568 && !mContentLengthOverLimit) {
569 ((ChunkedWritableByteChannel) getSink()).setCapacity(
570 (int) mContentLength);
571 }
572
573 if (mOffset != 0) {
574 // The server may ignore the request for a byte range, in which
575 // case status code will be 200, instead of 206. Note that we
576 // cannot call getHttpStatusCode as it rewrites 206 into 200.
577 if (mHttpStatusCode == 200) {
578 // TODO(mef): Revisit this logic.
579 if (mContentLength != -1) {
580 mContentLength -= mOffset;
581 }
582 mSkippingToOffset = true;
583 } else {
584 mSize = mOffset;
585 }
586 }
587 mListener.onResponseStarted(this);
588 } catch (Exception e) {
589 onCalledByNativeException(e);
590 }
591 }
592
593 /**
594 * Consumes a portion of the response.
595 *
596 * @param byteBuffer The ByteBuffer to append. Must be a direct buffer, and
597 * no references to it may be retained after the method ends, as
598 * it wraps code managed on the native heap.
599 */
600 @CalledByNative
601 private void onBytesRead(ByteBuffer buffer) {
602 try {
603 if (mContentLengthOverLimit) {
604 return;
605 }
606
607 int size = buffer.remaining();
608 mSize += size;
609 if (mSkippingToOffset) {
610 if (mSize <= mOffset) {
611 return;
612 } else {
613 mSkippingToOffset = false;
614 buffer.position((int) (mOffset - (mSize - size)));
615 }
616 }
617
618 boolean contentLengthOverLimit =
619 (mContentLengthLimit != 0 && mSize > mContentLengthLimit);
620 if (contentLengthOverLimit) {
621 buffer.limit(size - (int) (mSize - mContentLengthLimit));
622 }
623
624 while (buffer.hasRemaining()) {
625 mSink.write(buffer);
626 }
627 if (contentLengthOverLimit) {
628 onContentLengthOverLimit();
629 }
630 } catch (Exception e) {
631 onCalledByNativeException(e);
632 }
633 }
634
635 /**
636 * Notifies the listener, releases native data structures.
637 */
638 @SuppressWarnings("unused")
639 @CalledByNative
640 private void finish() {
641 try {
642 synchronized (mLock) {
643 if (mDisableRedirects) {
644 mHeadersAvailable = true;
645 }
646 mFinished = true;
647
648 if (mUrlRequestAdapter == 0) {
649 return;
650 }
651 try {
652 mSink.close();
653 } catch (IOException e) {
654 // Ignore
655 }
656 try {
657 if (mUploadChannel != null && mUploadChannel.isOpen()) {
658 mUploadChannel.close();
659 }
660 } catch (IOException e) {
661 // Ignore
662 }
663 onRequestComplete();
664 nativeDestroyRequestAdapter(mUrlRequestAdapter);
665 mUrlRequestAdapter = 0;
666 }
667 } catch (Exception e) {
668 mSinkException = new IOException("Exception in finish", e);
669 }
670 }
671
672 /**
673 * Appends header |name| with value |value| to |headersMap|.
674 */
675 @SuppressWarnings("unused")
676 @CalledByNative
677 private void onAppendResponseHeader(ResponseHeadersMap headersMap,
678 String name, String value) {
679 try {
680 if (!headersMap.containsKey(name)) {
681 headersMap.put(name, new ArrayList<String>());
682 }
683 headersMap.get(name).add(value);
684 } catch (Exception e) {
685 onCalledByNativeException(e);
686 }
687 }
688
689 /**
690 * Reads a sequence of bytes from upload channel into the given buffer.
691 * @param dest The buffer into which bytes are to be transferred.
692 * @return Returns number of bytes read (could be 0) or -1 and closes
693 * the channel if error occured.
694 */
695 @SuppressWarnings("unused")
696 @CalledByNative
697 private int readFromUploadChannel(ByteBuffer dest) {
698 try {
699 if (mUploadChannel == null || !mUploadChannel.isOpen()) return -1;
700 int result = mUploadChannel.read(dest);
701 if (result < 0) {
702 mUploadChannel.close();
703 return 0;
704 }
705 return result;
706 } catch (Exception e) {
707 onCalledByNativeException(e);
708 }
709 return -1;
710 }
711
712 // Native methods are implemented in chromium_url_request.cc.
713
714 private native long nativeCreateRequestAdapter(
715 long urlRequestContextAdapter, String url, int priority);
716
717 private native void nativeAddHeader(long urlRequestAdapter, String name,
718 String value);
719
720 private native void nativeSetMethod(long urlRequestAdapter, String method);
721
722 private native void nativeSetUploadData(long urlRequestAdapter,
723 String contentType, byte[] content);
724
725 private native void nativeSetUploadChannel(long urlRequestAdapter,
726 String contentType, long contentLength);
727
728 private native void nativeEnableChunkedUpload(long urlRequestAdapter,
729 String contentType);
730
731 private native void nativeDisableRedirects(long urlRequestAdapter);
732
733 private native void nativeAppendChunk(long urlRequestAdapter,
734 ByteBuffer chunk, int chunkSize, boolean isLastChunk);
735
736 private native void nativeStart(long urlRequestAdapter);
737
738 private native void nativeCancel(long urlRequestAdapter);
739
740 private native void nativeDestroyRequestAdapter(long urlRequestAdapter);
741
742 private native int nativeGetErrorCode(long urlRequestAdapter);
743
744 private native int nativeGetHttpStatusCode(long urlRequestAdapter);
745
746 private native String nativeGetHttpStatusText(long urlRequestAdapter);
747
748 private native String nativeGetErrorString(long urlRequestAdapter);
749
750 private native String nativeGetContentType(long urlRequestAdapter);
751
752 private native long nativeGetContentLength(long urlRequestAdapter);
753
754 private native String nativeGetHeader(long urlRequestAdapter, String name);
755
756 private native void nativeGetAllHeaders(long urlRequestAdapter,
757 ResponseHeadersMap headers);
758
759 private native String nativeGetNegotiatedProtocol(long urlRequestAdapter);
760
761 private native boolean nativeGetWasCached(long urlRequestAdapter);
762
763 // Explicit class to work around JNI-generator generics confusion.
764 private static class ResponseHeadersMap extends
765 HashMap<String, List<String>> {
766 }
767 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698