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; | |
8 | |
9 import org.chromium.base.CalledByNative; | |
10 import org.chromium.base.JNINamespace; | |
11 | |
12 import java.nio.ByteBuffer; | |
13 import java.util.AbstractMap; | |
14 import java.util.ArrayList; | |
15 import java.util.HashMap; | |
16 import java.util.List; | |
17 import java.util.Map; | |
18 import java.util.concurrent.Executor; | |
19 | |
7 /** | 20 /** |
8 * UrlRequest using Chromium HTTP stack implementation. | 21 * UrlRequest using Chromium HTTP stack implementation. Could be accessed from |
22 * any thread on Executor. Cancel can be done from any thread. | |
23 * All @CallByNative methods are called on native network thread | |
24 * and post tasks with listener calls onto Executor. Upon return from listener | |
25 * callback native request adapter is called on executive thread and posts | |
26 * native tasks to native network thread. Because Cancel could be called from | |
27 * any thread it is protected by mUrlRequestAdapterLock. | |
mmenke
2014/11/06 16:08:27
I think this comment can be cleaned up. I can pro
mef
2014/11/06 17:07:47
I'll appreciate that.
| |
9 */ | 28 */ |
10 public class CronetUrlRequest implements UrlRequest { | 29 @JNINamespace("cronet") |
30 final class CronetUrlRequest implements UrlRequest { | |
31 /** Native adapter object, owned by UrlRequest. */ | |
mmenke
2014/11/06 16:08:27
Should this use the "/**"? I assume the others do
mef
2014/11/06 22:51:46
Done.
| |
32 private long mUrlRequestAdapter; | |
33 private boolean mStarted = false; | |
34 private boolean mCanceled = false; | |
35 private boolean mInOnDataReceived = false; | |
36 private boolean mDestroyRequestAdapterAfterOnDataReceived = false; | |
37 /* | |
38 * Synchronize access to mUrlRequestAdapter, mStarted, mCanceled and | |
39 * mDestroyAfterReading. | |
40 */ | |
41 private final Object mUrlRequestAdapterLock = new Object(); | |
42 private final CronetUrlRequestContext mRequestContext; | |
43 private final Executor mExecutor; | |
44 /* | |
mmenke
2014/11/06 16:08:27
Suggest a blank line before each of these multi-li
mef
2014/11/06 22:51:47
Done.
| |
45 * Url chain contans the URL currently being requested, and | |
46 * all URLs previously requested. New URLs are only added after it is | |
47 * decided a redirect will be followed. | |
48 */ | |
49 private final List<String> mUrlChain = new ArrayList<String>(); | |
50 | |
51 private final UrlRequestListener mListener; | |
52 private final String mInitialUrl; | |
53 private final int mPriority; | |
54 private String mInitialMethod; | |
55 private final HeadersList mRequestHeaders = new HeadersList(); | |
56 | |
57 private NativeResponseInfo mResponseInfo; | |
58 | |
59 /* | |
60 * Listener callback is repeatedly called when data is received, so it is | |
61 * cached as member variable. | |
62 */ | |
63 private OnDataReceivedRunnable mOnDataReceivedTask; | |
64 | |
65 static final class HeaderEntry extends | |
66 AbstractMap.SimpleEntry<String, String> { | |
67 public HeaderEntry(String name, String value) { | |
68 super(name, value); | |
69 } | |
70 } | |
71 | |
72 static final class HeadersList extends ArrayList<HeaderEntry> { | |
73 } | |
74 | |
75 final class OnDataReceivedRunnable implements Runnable { | |
76 ByteBuffer mByteBuffer; | |
77 | |
78 public void run() { | |
79 if (isCanceled()) { | |
80 return; | |
81 } | |
82 try { | |
83 synchronized (mUrlRequestAdapterLock) { | |
84 if (mUrlRequestAdapter == 0) { | |
85 return; | |
86 } | |
87 mInOnDataReceived = true; | |
mmenke
2014/11/06 16:08:27
Should explicitly mention why this is needed.
mef
2014/11/06 22:51:46
Done.
| |
88 } | |
89 mListener.onDataReceived(CronetUrlRequest.this, | |
90 mResponseInfo, mByteBuffer); | |
91 mByteBuffer = null; | |
92 synchronized (mUrlRequestAdapterLock) { | |
93 mInOnDataReceived = false; | |
94 if (mDestroyRequestAdapterAfterOnDataReceived) { | |
95 destroyRequestAdapter(); | |
96 } | |
97 if (isCanceled()) { | |
mmenke
2014/11/06 16:08:27
Can just merge these and get rid of mDestroyReques
mef
2014/11/06 22:51:46
Done.
| |
98 return; | |
99 } | |
100 nativeReceiveData(mUrlRequestAdapter); | |
101 } | |
102 } catch (Exception e) { | |
103 synchronized (mUrlRequestAdapterLock) { | |
104 mInOnDataReceived = false; | |
105 if (mDestroyRequestAdapterAfterOnDataReceived) { | |
106 destroyRequestAdapter(); | |
107 } | |
108 } | |
109 onCalledByNativeException(e); | |
110 } | |
111 } | |
112 } | |
113 | |
114 static final class NativeResponseInfo implements ResponseInfo { | |
115 private final String[] mResponseInfoUrlChain; | |
116 private final int mHttpStatusCode; | |
117 private final HeadersMap mAllHeaders = new HeadersMap(); | |
118 private final boolean mWasCached; | |
119 private final String mNegotiatedProtocol; | |
120 | |
121 NativeResponseInfo(String[] urlChain, int httpStatusCode, | |
122 boolean wasCached, String negotiatedProtocol) { | |
123 mResponseInfoUrlChain = urlChain; | |
124 mHttpStatusCode = httpStatusCode; | |
125 mWasCached = wasCached; | |
126 mNegotiatedProtocol = negotiatedProtocol; | |
127 } | |
128 | |
129 @Override | |
130 public String getUrl() { | |
131 return mResponseInfoUrlChain[mResponseInfoUrlChain.length - 1]; | |
132 } | |
133 | |
134 @Override | |
135 public String[] getUrlChain() { | |
136 return mResponseInfoUrlChain; | |
137 } | |
138 | |
139 @Override | |
140 public int getHttpStatusCode() { | |
141 return mHttpStatusCode; | |
142 } | |
143 | |
144 @Override | |
145 public Map<String, List<String>> getAllHeaders() { | |
146 return mAllHeaders; | |
147 } | |
148 | |
149 @Override | |
150 public boolean wasCached() { | |
151 return mWasCached; | |
152 } | |
153 | |
154 @Override | |
155 public String getNegotiatedProtocol() { | |
156 return mNegotiatedProtocol; | |
157 } | |
158 }; | |
159 | |
160 static final class NativeExtendedResponseInfo implements | |
161 ExtendedResponseInfo { | |
162 private final ResponseInfo mResponseInfo; | |
163 private final long mTotalReceivedBytes; | |
164 | |
165 NativeExtendedResponseInfo(ResponseInfo responseInfo, | |
166 long totalReceivedBytes) { | |
167 mResponseInfo = responseInfo; | |
168 mTotalReceivedBytes = totalReceivedBytes; | |
169 } | |
170 | |
171 @Override | |
172 public ResponseInfo getResponseInfo() { | |
173 return mResponseInfo; | |
174 } | |
175 | |
176 @Override | |
177 public long getTotalReceivedBytes() { | |
178 return mTotalReceivedBytes; | |
179 } | |
180 }; | |
181 | |
182 CronetUrlRequest(CronetUrlRequestContext requestContext, | |
183 long urlRequestContextAdapter, | |
184 String url, | |
185 int priority, | |
186 UrlRequestListener listener, | |
187 Executor executor) { | |
188 if (url == null) { | |
189 throw new NullPointerException("URL is required"); | |
190 } | |
191 if (listener == null) { | |
192 throw new NullPointerException("Listener is required"); | |
193 } | |
194 if (executor == null) { | |
195 throw new NullPointerException("Executor is required"); | |
196 } | |
197 | |
198 mRequestContext = requestContext; | |
199 mInitialUrl = url; | |
200 mUrlChain.add(url); | |
201 mPriority = convertRequestPriority(priority); | |
202 mListener = listener; | |
203 mExecutor = executor; | |
204 } | |
205 | |
11 @Override | 206 @Override |
12 public void setHttpMethod(String method) { | 207 public void setHttpMethod(String method) { |
13 | 208 checkNotStarted(); |
209 if (method == null) { | |
210 throw new NullPointerException("Method is required."); | |
211 } | |
212 mInitialMethod = method; | |
14 } | 213 } |
15 | 214 |
16 @Override | 215 @Override |
17 public void addHeader(String header, String value) { | 216 public void addHeader(String header, String value) { |
18 | 217 checkNotStarted(); |
19 } | 218 if (header == null) { |
20 | 219 throw new NullPointerException("Invalid header name."); |
21 @Override | 220 } |
22 public void start(UrlRequestListener listener) { | 221 if (value == null) { |
23 | 222 throw new NullPointerException("Invalid header value."); |
223 } | |
224 mRequestHeaders.add(new HeaderEntry(header, value)); | |
225 } | |
226 | |
227 @Override | |
228 public void start() { | |
229 synchronized (mUrlRequestAdapterLock) { | |
230 if (mUrlRequestAdapter != 0) { | |
mmenke
2014/11/06 16:08:27
Should check mStarted instead (It prevents restart
mef
2014/11/06 22:51:46
Done.
| |
231 throw new IllegalStateException("Request is already started."); | |
232 } | |
233 mUrlRequestAdapter = nativeCreateRequestAdapter( | |
234 mRequestContext.getUrlRequestContextAdapter(), | |
235 mInitialUrl, | |
236 mPriority); | |
237 if (mInitialMethod != null) { | |
238 if (!nativeSetHttpMethod(mUrlRequestAdapter, mInitialMethod)) { | |
239 nativeDestroyRequestAdapter(mUrlRequestAdapter); | |
240 mUrlRequestAdapter = 0; | |
mmenke
2014/11/06 16:08:27
Replace these two lines with destroyRequestAdapter
mef
2014/11/06 22:51:46
Done.
| |
241 throw new IllegalArgumentException("Invalid http method " | |
242 + mInitialMethod); | |
243 } | |
244 } | |
245 for (HeaderEntry header : mRequestHeaders) { | |
246 if (!nativeAddHeader(mUrlRequestAdapter, header.getKey(), | |
247 header.getValue())) { | |
248 nativeDestroyRequestAdapter(mUrlRequestAdapter); | |
249 mUrlRequestAdapter = 0; | |
250 throw new IllegalArgumentException("Invalid header " | |
251 + header.getKey() + "=" + header.getValue()); | |
252 } | |
253 } | |
254 mStarted = true; | |
255 nativeStart(mUrlRequestAdapter); | |
256 mRequestContext.onRequestStarted(this); | |
257 } | |
24 } | 258 } |
25 | 259 |
26 @Override | 260 @Override |
27 public void cancel() { | 261 public void cancel() { |
28 | 262 synchronized (mUrlRequestAdapterLock) { |
263 if (mCanceled) { | |
264 return; | |
265 } | |
266 mCanceled = true; | |
267 // During call into listener OnDataReceived adapter cannot be | |
268 // destroyed as it owns the byte buffer. | |
269 if (mInOnDataReceived) { | |
270 mDestroyRequestAdapterAfterOnDataReceived = true; | |
271 } else { | |
272 destroyRequestAdapter(); | |
273 } | |
274 } | |
29 } | 275 } |
30 | 276 |
31 @Override | 277 @Override |
32 public boolean isCanceled() { | 278 public boolean isCanceled() { |
33 return false; | 279 synchronized (mUrlRequestAdapterLock) { |
280 return mCanceled; | |
281 } | |
34 } | 282 } |
35 | 283 |
36 @Override | 284 @Override |
37 public void pause() { | 285 public void pause() { |
38 | 286 throw new UnsupportedOperationException("Not implemented yet"); |
39 } | 287 } |
40 | 288 |
41 @Override | 289 @Override |
42 public boolean isPaused() { | 290 public boolean isPaused() { |
43 return false; | 291 return false; |
44 } | 292 } |
45 | 293 |
46 @Override | 294 @Override |
47 public void resume() { | 295 public void resume() { |
48 | 296 throw new UnsupportedOperationException("Not implemented yet"); |
297 } | |
298 | |
299 /** | |
300 * Post task to application Executor. Used for Listener callbacks | |
301 * and other tasks that should not be executed on network thread. | |
302 */ | |
303 private void postTaskToExecutor(Runnable task) { | |
304 mExecutor.execute(task); | |
305 } | |
306 | |
307 private static int convertRequestPriority(int priority) { | |
308 switch (priority) { | |
309 case REQUEST_PRIORITY_IDLE: | |
310 return RequestPriority.IDLE; | |
311 case REQUEST_PRIORITY_LOWEST: | |
312 return RequestPriority.LOWEST; | |
313 case REQUEST_PRIORITY_LOW: | |
314 return RequestPriority.LOW; | |
315 case REQUEST_PRIORITY_MEDIUM: | |
316 return RequestPriority.MEDIUM; | |
317 case REQUEST_PRIORITY_HIGHEST: | |
318 return RequestPriority.HIGHEST; | |
319 default: | |
320 return RequestPriority.MEDIUM; | |
321 } | |
322 } | |
323 | |
324 private NativeResponseInfo prepareResponseInfo(int httpStatusCode) { | |
mmenke
2014/11/06 16:08:27
Should either rename network thread methods to mak
mef
2014/11/06 22:51:46
Done.
| |
325 long urlRequestAdapter; | |
326 synchronized (mUrlRequestAdapterLock) { | |
327 if (mUrlRequestAdapter == 0) { | |
328 return null; | |
329 } | |
330 urlRequestAdapter = mUrlRequestAdapter; | |
331 } | |
332 NativeResponseInfo responseInfo = new NativeResponseInfo( | |
333 mUrlChain.toArray(new String[mUrlChain.size()]), | |
334 httpStatusCode, | |
335 nativeGetWasCached(urlRequestAdapter), | |
336 nativeGetNegotiatedProtocol(urlRequestAdapter)); | |
mmenke
2014/11/06 16:08:27
Should mention why all these calls into the adapte
mef
2014/11/06 22:51:46
Done.
| |
337 nativePopulateResponseHeaders(urlRequestAdapter, | |
338 responseInfo.mAllHeaders); | |
339 return responseInfo; | |
340 } | |
341 | |
342 private void checkNotStarted() { | |
343 synchronized (mUrlRequestAdapterLock) { | |
344 if (mStarted) { | |
345 throw new IllegalStateException("Request is already started."); | |
346 } | |
mmenke
2014/11/06 16:08:26
Should we check if we're cancelled, too? Seems li
mef
2014/11/06 22:51:46
Done.
| |
347 } | |
348 } | |
349 | |
350 private void destroyRequestAdapter() { | |
351 synchronized (mUrlRequestAdapterLock) { | |
352 if (mUrlRequestAdapter == 0) { | |
353 return; | |
354 } | |
355 nativeDestroyRequestAdapter(mUrlRequestAdapter); | |
356 mRequestContext.onRequestDestroyed(this); | |
357 mUrlRequestAdapter = 0; | |
358 } | |
359 } | |
360 | |
361 private void appendHeaderToMap(HeadersMap headersMap, | |
mmenke
2014/11/06 16:08:27
static...Actually, I suggest just inlining this, t
mef
2014/11/06 22:51:46
Done.
| |
362 String name, String value) { | |
363 if (!headersMap.containsKey(name)) { | |
364 headersMap.put(name, new ArrayList<String>()); | |
365 } | |
366 headersMap.get(name).add(value); | |
367 } | |
368 | |
369 /** | |
370 * If @CalledByNative method throws an exception, request gets canceled | |
371 * and exception could be retrieved from request using getException(). | |
mmenke
2014/11/06 16:08:27
"Only called on the Executor".
mmenke
2014/11/06 16:08:27
There is no GetException method. We actually pass
mef
2014/11/06 22:51:46
Done.
| |
372 */ | |
373 private void onCalledByNativeException(Exception e) { | |
mmenke
2014/11/06 16:08:27
If the request has already been cancelled, we shou
mmenke
2014/11/06 16:08:27
Both the name and description aren't terribly accu
mef
2014/11/06 22:51:46
Done.
| |
374 UrlRequestException requestError = new UrlRequestException( | |
375 "CalledByNative method has thrown an exception", e); | |
376 Log.e(CronetUrlRequestContext.LOG_TAG, | |
377 "Exception in CalledByNative method", e); | |
378 try { | |
379 cancel(); | |
380 mListener.onFailed(this, mResponseInfo, requestError); | |
381 } catch (Exception cancelException) { | |
382 Log.e(CronetUrlRequestContext.LOG_TAG, | |
383 "Exception trying to cancel request", cancelException); | |
384 } | |
385 } | |
386 | |
387 //////////////////////////////////////////////// | |
388 // Private methods called by the native code. | |
389 //////////////////////////////////////////////// | |
390 | |
391 /** | |
392 * Called before following redirects. The redirect will automatically be | |
393 * followed, unless the request is paused or canceled during this | |
394 * callback. If the redirect response has a body, it will be ignored. | |
395 * This will only be called between start and onResponseStarted. | |
396 * | |
397 * @param newLocation Location where request is redirected. | |
398 * @param httpStatusCode from redirect response | |
399 */ | |
400 @SuppressWarnings("unused") | |
401 @CalledByNative | |
402 private void onRedirect(final String newLocation, int httpStatusCode) { | |
403 final NativeResponseInfo responseInfo = | |
404 prepareResponseInfo(httpStatusCode); | |
405 Runnable task = new Runnable() { | |
406 public void run() { | |
407 if (isCanceled()) { | |
408 return; | |
409 } | |
410 try { | |
411 mListener.onRedirect(CronetUrlRequest.this, responseInfo, | |
412 newLocation); | |
413 synchronized (mUrlRequestAdapterLock) { | |
414 if (isCanceled()) { | |
415 return; | |
416 } | |
417 // It is Ok to access mUrlChain not on the network | |
418 // thread as the request is waiting to follow redirect. | |
419 mUrlChain.add(newLocation); | |
420 nativeFollowDeferredRedirect(mUrlRequestAdapter); | |
421 } | |
422 } catch (Exception e) { | |
423 onCalledByNativeException(e); | |
424 } | |
425 } | |
426 }; | |
427 postTaskToExecutor(task); | |
428 } | |
429 | |
430 /** | |
431 * Called when the final set of headers, after all redirects, | |
432 * is received. Can only be called once for each request. | |
433 */ | |
434 @SuppressWarnings("unused") | |
435 @CalledByNative | |
436 private void onResponseStarted(int httpStatusCode) { | |
437 mResponseInfo = prepareResponseInfo(httpStatusCode); | |
438 Runnable task = new Runnable() { | |
439 public void run() { | |
440 if (isCanceled()) { | |
441 return; | |
442 } | |
443 try { | |
444 mListener.onResponseStarted(CronetUrlRequest.this, | |
445 mResponseInfo); | |
446 synchronized (mUrlRequestAdapterLock) { | |
447 if (isCanceled()) { | |
448 return; | |
449 } | |
450 nativeReceiveData(mUrlRequestAdapter); | |
451 } | |
452 } catch (Exception e) { | |
453 onCalledByNativeException(e); | |
454 } | |
455 } | |
456 }; | |
457 postTaskToExecutor(task); | |
458 } | |
459 | |
460 /** | |
461 * Called whenever data is received. The ByteBuffer remains | |
462 * valid only until listener callback. Or if the callback | |
463 * pauses the request, it remains valid until the request is resumed. | |
464 * Cancelling the request also invalidates the buffer. | |
465 * | |
466 * @param byteBuffer Received data. | |
467 */ | |
468 @SuppressWarnings("unused") | |
469 @CalledByNative | |
470 private void onDataReceived(final ByteBuffer byteBuffer) { | |
471 if (mOnDataReceivedTask == null) { | |
472 mOnDataReceivedTask = new OnDataReceivedRunnable(); | |
473 } | |
474 mOnDataReceivedTask.mByteBuffer = byteBuffer; | |
475 postTaskToExecutor(mOnDataReceivedTask); | |
476 } | |
477 | |
478 /** | |
479 * Called when request is complete, no callbacks will be called afterwards. | |
480 */ | |
481 @SuppressWarnings("unused") | |
482 @CalledByNative | |
483 private void onComplete() { | |
mmenke
2014/11/06 16:08:27
onSucceeded?
mef
2014/11/06 22:51:47
Done.
| |
484 long totalReceivedBytes; | |
485 synchronized (mUrlRequestAdapterLock) { | |
486 if (mUrlRequestAdapter == 0) { | |
487 return; | |
488 } | |
489 totalReceivedBytes = | |
490 nativeGetTotalReceivedBytes(mUrlRequestAdapter); | |
491 } | |
492 | |
493 final NativeExtendedResponseInfo extendedResponseInfo = | |
494 new NativeExtendedResponseInfo(mResponseInfo, | |
495 totalReceivedBytes); | |
496 Runnable task = new Runnable() { | |
497 public void run() { | |
498 if (isCanceled()) { | |
499 return; | |
500 } | |
501 // Destroy adapter first, so request context could be shut down | |
502 // from the listener. | |
503 destroyRequestAdapter(); | |
mmenke
2014/11/06 16:08:27
Hrm...Does it make sense to grab the lock, check i
mef
2014/11/06 22:51:46
Done.
| |
504 try { | |
505 mListener.onSucceeded(CronetUrlRequest.this, | |
506 extendedResponseInfo); | |
507 } catch (Exception e) { | |
508 Log.e(CronetUrlRequestContext.LOG_TAG, | |
509 "Exception in onComplete method", e); | |
510 } | |
511 } | |
512 }; | |
513 postTaskToExecutor(task); | |
514 } | |
515 | |
516 /** | |
517 * Called when error has occured, no callbacks will be called afterwards. | |
518 * | |
519 * @param nativeError native net error code. | |
520 * @param errorString textual representation of the error code. | |
521 */ | |
522 @SuppressWarnings("unused") | |
523 @CalledByNative | |
524 private void onError(final int nativeError, final String errorString) { | |
525 Runnable task = new Runnable() { | |
526 public void run() { | |
527 if (isCanceled()) { | |
528 return; | |
529 } | |
530 // Destroy adapter first, so request context could be shut down | |
531 // from the listener. | |
532 destroyRequestAdapter(); | |
533 try { | |
534 UrlRequestException requestError = new UrlRequestException( | |
535 "Exception in CronetUrlRequest: " + errorString, | |
536 nativeError); | |
537 mListener.onFailed(CronetUrlRequest.this, | |
538 mResponseInfo, | |
539 requestError); | |
540 } catch (Exception e) { | |
541 Log.e(CronetUrlRequestContext.LOG_TAG, | |
542 "Exception in onError method", e); | |
543 } | |
544 } | |
545 }; | |
546 postTaskToExecutor(task); | |
547 } | |
548 | |
549 /** | |
550 * Appends header |name| with value |value| to |headersMap|. | |
551 */ | |
552 @SuppressWarnings("unused") | |
553 @CalledByNative | |
554 private void onAppendResponseHeader(HeadersMap headersMap, | |
555 String name, String value) { | |
556 try { | |
mmenke
2014/11/06 16:08:27
Is this try block even needed? This is a weird en
mef
2014/11/06 17:07:47
Hmm, maybe not.
mef
2014/11/06 22:51:47
Done.
| |
557 appendHeaderToMap(headersMap, name, value); | |
558 } catch (final Exception e) { | |
559 onCalledByNativeException(e); | |
560 Runnable task = new Runnable() { | |
561 public void run() { | |
562 if (isCanceled()) { | |
563 return; | |
564 } | |
565 onCalledByNativeException(e); | |
566 } | |
567 }; | |
568 postTaskToExecutor(task); | |
569 } | |
570 } | |
571 | |
572 // Native methods are implemented in cronet_url_request.cc. | |
573 | |
574 private native long nativeCreateRequestAdapter( | |
575 long urlRequestContextAdapter, String url, int priority); | |
576 | |
577 private native boolean nativeAddHeader(long urlRequestAdapter, String name, | |
578 String value); | |
579 | |
580 private native boolean nativeSetHttpMethod(long urlRequestAdapter, | |
581 String method); | |
582 | |
583 private native void nativeStart(long urlRequestAdapter); | |
584 | |
585 private native void nativeDestroyRequestAdapter(long urlRequestAdapter); | |
586 | |
587 private native void nativeFollowDeferredRedirect(long urlRequestAdapter); | |
588 | |
589 private native void nativeReceiveData(long urlRequestAdapter); | |
590 | |
591 private native void nativePopulateResponseHeaders(long urlRequestAdapter, | |
592 HeadersMap headers); | |
593 | |
594 private native String nativeGetNegotiatedProtocol(long urlRequestAdapter); | |
595 | |
596 private native boolean nativeGetWasCached(long urlRequestAdapter); | |
597 | |
598 private native long nativeGetTotalReceivedBytes(long urlRequestAdapter); | |
599 | |
600 // Explicit class to work around JNI-generator generics confusion. | |
601 private static class HeadersMap extends HashMap<String, List<String>> { | |
49 } | 602 } |
50 } | 603 } |
OLD | NEW |