• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2014 The Chromium Authors
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.impl;
6 
7 import static java.lang.Math.max;
8 import static org.chromium.net.UrlRequest.Builder.REQUEST_PRIORITY_IDLE;
9 import static org.chromium.net.UrlRequest.Builder.REQUEST_PRIORITY_LOWEST;
10 import static org.chromium.net.UrlRequest.Builder.REQUEST_PRIORITY_LOW;
11 import static org.chromium.net.UrlRequest.Builder.REQUEST_PRIORITY_MEDIUM;
12 import static org.chromium.net.UrlRequest.Builder.REQUEST_PRIORITY_HIGHEST;
13 
14 import android.os.Build;
15 
16 import androidx.annotation.RequiresApi;
17 import androidx.annotation.VisibleForTesting;
18 
19 import org.jni_zero.CalledByNative;
20 import org.jni_zero.JNINamespace;
21 import org.jni_zero.NativeClassQualifiedName;
22 import org.jni_zero.NativeMethods;
23 
24 import org.chromium.base.Log;
25 import org.chromium.net.CallbackException;
26 import org.chromium.net.CronetException;
27 import org.chromium.net.Idempotency;
28 import org.chromium.net.InlineExecutionProhibitedException;
29 import org.chromium.net.NetworkException;
30 import org.chromium.net.RequestFinishedInfo;
31 import org.chromium.net.RequestPriority;
32 import org.chromium.net.UploadDataProvider;
33 import org.chromium.net.UrlRequest;
34 import org.chromium.net.UrlResponseInfo.HeaderBlock;
35 import org.chromium.net.impl.CronetLogger.CronetTrafficInfo;
36 
37 import java.nio.ByteBuffer;
38 import java.time.Duration;
39 import java.util.AbstractMap;
40 import java.util.ArrayList;
41 import java.util.Collection;
42 import java.util.Collections;
43 import java.util.List;
44 import java.util.Map;
45 import java.util.Objects;
46 import java.util.concurrent.Executor;
47 import java.util.concurrent.RejectedExecutionException;
48 
49 import javax.annotation.concurrent.GuardedBy;
50 
51 /**
52  * UrlRequest using Chromium HTTP stack implementation. Could be accessed from
53  * any thread on Executor. Cancel can be called from any thread.
54  * All @CallByNative methods are called on native network thread
55  * and post tasks with listener calls onto Executor. Upon return from listener
56  * callback native request adapter is called on executive thread and posts
57  * native tasks to native network thread. Because Cancel could be called from
58  * any thread it is protected by mUrlRequestAdapterLock.
59  */
60 @JNINamespace("cronet")
61 @VisibleForTesting
62 public final class CronetUrlRequest extends UrlRequestBase {
63     private final boolean mAllowDirectExecutor;
64 
65     /* Native adapter object, owned by UrlRequest. */
66     @GuardedBy("mUrlRequestAdapterLock")
67     private long mUrlRequestAdapter;
68 
69     @GuardedBy("mUrlRequestAdapterLock")
70     private boolean mStarted;
71 
72     @GuardedBy("mUrlRequestAdapterLock")
73     private boolean mWaitingOnRedirect;
74 
75     @GuardedBy("mUrlRequestAdapterLock")
76     private boolean mWaitingOnRead;
77 
78     /*
79      * Synchronize access to mUrlRequestAdapter, mStarted, mWaitingOnRedirect,
80      * and mWaitingOnRead.
81      */
82     private final Object mUrlRequestAdapterLock = new Object();
83     private final CronetUrlRequestContext mRequestContext;
84     private final Executor mExecutor;
85 
86     /*
87      * URL chain contains the URL currently being requested, and
88      * all URLs previously requested. New URLs are added before
89      * mCallback.onRedirectReceived is called.
90      */
91     private final List<String> mUrlChain = new ArrayList<String>();
92 
93     private final VersionSafeCallbacks.UrlRequestCallback mCallback;
94     private final String mInitialUrl;
95     private final int mPriority;
96     private final int mIdempotency;
97     private String mInitialMethod;
98     private final HeadersList mRequestHeaders = new HeadersList();
99     private final Collection<Object> mRequestAnnotations;
100     private final boolean mDisableCache;
101     private final boolean mDisableConnectionMigration;
102     private final boolean mTrafficStatsTagSet;
103     private final int mTrafficStatsTag;
104     private final boolean mTrafficStatsUidSet;
105     private final int mTrafficStatsUid;
106     private final VersionSafeCallbacks.RequestFinishedInfoListener mRequestFinishedListener;
107     private final long mNetworkHandle;
108     private final int mCronetEngineId;
109     private final CronetLogger mLogger;
110 
111     private CronetUploadDataStream mUploadDataStream;
112 
113     private UrlResponseInfoImpl mResponseInfo;
114 
115     // These three should only be updated once with mUrlRequestAdapterLock held. They are read on
116     // UrlRequest.Callback's and RequestFinishedInfo.Listener's executors after the last update.
117     @RequestFinishedInfoImpl.FinishedReason private int mFinishedReason;
118     private CronetException mException;
119     private CronetMetrics mMetrics;
120     private boolean mQuicConnectionMigrationAttempted;
121     private boolean mQuicConnectionMigrationSuccessful;
122 
123     /*
124      * Listener callback is repeatedly invoked when each read is completed, so it
125      * is cached as a member variable.
126      */
127     private OnReadCompletedRunnable mOnReadCompletedTask;
128 
129     @GuardedBy("mUrlRequestAdapterLock")
130     private Runnable mOnDestroyedCallbackForTesting;
131 
132     @VisibleForTesting
133     public static final class HeadersList extends ArrayList<Map.Entry<String, String>> {}
134 
135     private final class OnReadCompletedRunnable implements Runnable {
136         // Buffer passed back from current invocation of onReadCompleted.
137         ByteBuffer mByteBuffer;
138 
139         @Override
run()140         public void run() {
141             checkCallingThread();
142             // Null out mByteBuffer, to pass buffer ownership to callback or release if done.
143             ByteBuffer buffer = mByteBuffer;
144             mByteBuffer = null;
145 
146             try {
147                 synchronized (mUrlRequestAdapterLock) {
148                     if (isDoneLocked()) {
149                         return;
150                     }
151                     mWaitingOnRead = true;
152                 }
153                 mCallback.onReadCompleted(CronetUrlRequest.this, mResponseInfo, buffer);
154             } catch (Exception e) {
155                 onCallbackException(e);
156             }
157         }
158     }
159 
CronetUrlRequest( CronetUrlRequestContext requestContext, String url, int priority, UrlRequest.Callback callback, Executor executor, Collection<Object> requestAnnotations, boolean disableCache, boolean disableConnectionMigration, boolean allowDirectExecutor, boolean trafficStatsTagSet, int trafficStatsTag, boolean trafficStatsUidSet, int trafficStatsUid, RequestFinishedInfo.Listener requestFinishedListener, int idempotency, long networkHandle)160     CronetUrlRequest(
161             CronetUrlRequestContext requestContext,
162             String url,
163             int priority,
164             UrlRequest.Callback callback,
165             Executor executor,
166             Collection<Object> requestAnnotations,
167             boolean disableCache,
168             boolean disableConnectionMigration,
169             boolean allowDirectExecutor,
170             boolean trafficStatsTagSet,
171             int trafficStatsTag,
172             boolean trafficStatsUidSet,
173             int trafficStatsUid,
174             RequestFinishedInfo.Listener requestFinishedListener,
175             int idempotency,
176             long networkHandle) {
177         Objects.requireNonNull(url, "URL is required");
178         Objects.requireNonNull(callback, "Listener is required");
179         Objects.requireNonNull(executor, "Executor is required");
180 
181         mAllowDirectExecutor = allowDirectExecutor;
182         mRequestContext = requestContext;
183         mCronetEngineId = requestContext.getCronetEngineId();
184         mLogger = requestContext.getCronetLogger();
185         mInitialUrl = url;
186         mUrlChain.add(url);
187         mPriority = convertRequestPriority(priority);
188         mCallback = new VersionSafeCallbacks.UrlRequestCallback(callback);
189         mExecutor = executor;
190         mRequestAnnotations = requestAnnotations;
191         mDisableCache = disableCache;
192         mDisableConnectionMigration = disableConnectionMigration;
193         mTrafficStatsTagSet = trafficStatsTagSet;
194         mTrafficStatsTag = trafficStatsTag;
195         mTrafficStatsUidSet = trafficStatsUidSet;
196         mTrafficStatsUid = trafficStatsUid;
197         mRequestFinishedListener =
198                 requestFinishedListener != null
199                         ? new VersionSafeCallbacks.RequestFinishedInfoListener(
200                                 requestFinishedListener)
201                         : null;
202         mIdempotency = convertIdempotency(idempotency);
203         mNetworkHandle = networkHandle;
204     }
205 
206     @Override
setHttpMethod(String method)207     public void setHttpMethod(String method) {
208         checkNotStarted();
209         Objects.requireNonNull(method, "Method is required.");
210         mInitialMethod = method;
211     }
212 
213     @Override
addHeader(String header, String value)214     public void addHeader(String header, String value) {
215         checkNotStarted();
216         Objects.requireNonNull(header, "Invalid header name.");
217         Objects.requireNonNull(value, "Invalid header value.");
218         mRequestHeaders.add(new AbstractMap.SimpleImmutableEntry<String, String>(header, value));
219     }
220 
221     @Override
setUploadDataProvider(UploadDataProvider uploadDataProvider, Executor executor)222     public void setUploadDataProvider(UploadDataProvider uploadDataProvider, Executor executor) {
223         Objects.requireNonNull(uploadDataProvider, "Invalid UploadDataProvider.");
224         if (mInitialMethod == null) {
225             mInitialMethod = "POST";
226         }
227         mUploadDataStream = new CronetUploadDataStream(uploadDataProvider, executor, this);
228     }
229 
230     @Override
getHttpMethod()231     public String getHttpMethod() {
232         return mInitialMethod;
233     }
234 
235     @Override
isDirectExecutorAllowed()236     public boolean isDirectExecutorAllowed() {
237         return mAllowDirectExecutor;
238     }
239 
240     @Override
isCacheDisabled()241     public boolean isCacheDisabled() {
242         return mDisableCache;
243     }
244 
245     @Override
hasTrafficStatsTag()246     public boolean hasTrafficStatsTag() {
247         return mTrafficStatsTagSet;
248     }
249 
250     @Override
getTrafficStatsTag()251     public int getTrafficStatsTag() {
252         if (!hasTrafficStatsTag()) {
253             throw new IllegalStateException("TrafficStatsTag is not set");
254         }
255         return mTrafficStatsTag;
256     }
257 
258     @Override
hasTrafficStatsUid()259     public boolean hasTrafficStatsUid() {
260         return mTrafficStatsUidSet;
261     }
262 
263     @Override
getTrafficStatsUid()264     public int getTrafficStatsUid() {
265         if (!hasTrafficStatsUid()) {
266             throw new IllegalStateException("TrafficStatsUid is not set");
267         }
268         return mTrafficStatsUid;
269     }
270     @Override
getPriority()271     public int getPriority() {
272         switch (mPriority) {
273             case RequestPriority.IDLE:
274                 return REQUEST_PRIORITY_IDLE;
275             case RequestPriority.LOWEST:
276                 return REQUEST_PRIORITY_LOWEST;
277             case RequestPriority.LOW:
278                 return REQUEST_PRIORITY_LOW;
279             case RequestPriority.MEDIUM:
280                 return REQUEST_PRIORITY_MEDIUM;
281             case RequestPriority.HIGHEST:
282                 return REQUEST_PRIORITY_HIGHEST;
283             default:
284                 throw new IllegalStateException("Invalid request priority: " + mPriority);
285         }
286     }
287 
288     @Override
getHeaders()289     public HeaderBlock getHeaders() {
290         return new UrlResponseInfoImpl.HeaderBlockImpl(mRequestHeaders);
291     }
292 
293     @Override
start()294     public void start() {
295         synchronized (mUrlRequestAdapterLock) {
296             checkNotStarted();
297 
298             try {
299                 mUrlRequestAdapter =
300                         CronetUrlRequestJni.get()
301                                 .createRequestAdapter(
302                                         CronetUrlRequest.this,
303                                         mRequestContext.getUrlRequestContextAdapter(),
304                                         mInitialUrl,
305                                         mPriority,
306                                         mDisableCache,
307                                         mDisableConnectionMigration,
308                                         mTrafficStatsTagSet,
309                                         mTrafficStatsTag,
310                                         mTrafficStatsUidSet,
311                                         mTrafficStatsUid,
312                                         mIdempotency,
313                                         mNetworkHandle);
314                 mRequestContext.onRequestStarted();
315                 if (mInitialMethod != null) {
316                     if (!CronetUrlRequestJni.get()
317                             .setHttpMethod(
318                                     mUrlRequestAdapter, CronetUrlRequest.this, mInitialMethod)) {
319                         throw new IllegalArgumentException("Invalid http method " + mInitialMethod);
320                     }
321                 }
322 
323                 boolean hasContentType = false;
324                 for (Map.Entry<String, String> header : mRequestHeaders) {
325                     if (header.getKey().equalsIgnoreCase("Content-Type")
326                             && !header.getValue().isEmpty()) {
327                         hasContentType = true;
328                     }
329                     if (!CronetUrlRequestJni.get()
330                             .addRequestHeader(
331                                     mUrlRequestAdapter,
332                                     CronetUrlRequest.this,
333                                     header.getKey(),
334                                     header.getValue())) {
335                         throw new IllegalArgumentException(
336                                 "Invalid header with headername: " + header.getKey());
337                     }
338                 }
339                 if (mUploadDataStream != null) {
340                     if (!hasContentType) {
341                         throw new IllegalArgumentException(
342                                 "Requests with upload data must have a Content-Type.");
343                     }
344                     mStarted = true;
345                     mUploadDataStream.postTaskToExecutor(
346                             new Runnable() {
347                                 @Override
348                                 public void run() {
349                                     mUploadDataStream.initializeWithRequest();
350                                     synchronized (mUrlRequestAdapterLock) {
351                                         if (isDoneLocked()) {
352                                             return;
353                                         }
354                                         mUploadDataStream.attachNativeAdapterToRequest(
355                                                 mUrlRequestAdapter);
356                                         startInternalLocked();
357                                     }
358                                 }
359                             });
360                     return;
361                 }
362             } catch (RuntimeException e) {
363                 // If there's an exception, cleanup and then throw the exception to the caller.
364                 // start() is synchronized so we do not acquire mUrlRequestAdapterLock here.
365                 destroyRequestAdapterLocked(RequestFinishedInfo.FAILED);
366                 mRequestContext.onRequestFinished();
367                 throw e;
368             }
369             mStarted = true;
370             startInternalLocked();
371         }
372     }
373 
374     /*
375      * Starts fully configured request. Could execute on UploadDataProvider executor.
376      * Caller is expected to ensure that request isn't canceled and mUrlRequestAdapter is valid.
377      */
378     @GuardedBy("mUrlRequestAdapterLock")
startInternalLocked()379     private void startInternalLocked() {
380         CronetUrlRequestJni.get().start(mUrlRequestAdapter, CronetUrlRequest.this);
381     }
382 
383     @Override
followRedirect()384     public void followRedirect() {
385         synchronized (mUrlRequestAdapterLock) {
386             if (!mWaitingOnRedirect) {
387                 throw new IllegalStateException("No redirect to follow.");
388             }
389             mWaitingOnRedirect = false;
390 
391             if (isDoneLocked()) {
392                 return;
393             }
394 
395             CronetUrlRequestJni.get()
396                     .followDeferredRedirect(mUrlRequestAdapter, CronetUrlRequest.this);
397         }
398     }
399 
400     @Override
read(ByteBuffer buffer)401     public void read(ByteBuffer buffer) {
402         Preconditions.checkHasRemaining(buffer);
403         Preconditions.checkDirect(buffer);
404         synchronized (mUrlRequestAdapterLock) {
405             if (!mWaitingOnRead) {
406                 throw new IllegalStateException("Unexpected read attempt.");
407             }
408             mWaitingOnRead = false;
409 
410             if (isDoneLocked()) {
411                 return;
412             }
413 
414             if (!CronetUrlRequestJni.get()
415                     .readData(
416                             mUrlRequestAdapter,
417                             CronetUrlRequest.this,
418                             buffer,
419                             buffer.position(),
420                             buffer.limit())) {
421                 // Still waiting on read. This is just to have consistent
422                 // behavior with the other error cases.
423                 mWaitingOnRead = true;
424                 throw new IllegalArgumentException("Unable to call native read");
425             }
426         }
427     }
428 
429     @Override
cancel()430     public void cancel() {
431         synchronized (mUrlRequestAdapterLock) {
432             if (isDoneLocked() || !mStarted) {
433                 return;
434             }
435             destroyRequestAdapterLocked(RequestFinishedInfo.CANCELED);
436         }
437     }
438 
439     @Override
isDone()440     public boolean isDone() {
441         synchronized (mUrlRequestAdapterLock) {
442             return isDoneLocked();
443         }
444     }
445 
446     @GuardedBy("mUrlRequestAdapterLock")
isDoneLocked()447     private boolean isDoneLocked() {
448         return mStarted && mUrlRequestAdapter == 0;
449     }
450 
451     @Override
getStatus(UrlRequest.StatusListener unsafeListener)452     public void getStatus(UrlRequest.StatusListener unsafeListener) {
453         final VersionSafeCallbacks.UrlRequestStatusListener listener =
454                 new VersionSafeCallbacks.UrlRequestStatusListener(unsafeListener);
455         synchronized (mUrlRequestAdapterLock) {
456             if (mUrlRequestAdapter != 0) {
457                 CronetUrlRequestJni.get()
458                         .getStatus(mUrlRequestAdapter, CronetUrlRequest.this, listener);
459                 return;
460             }
461         }
462         Runnable task =
463                 new Runnable() {
464                     @Override
465                     public void run() {
466                         listener.onStatus(UrlRequest.Status.INVALID);
467                     }
468                 };
469         postTaskToExecutor(task);
470     }
471 
setOnDestroyedCallbackForTesting(Runnable onDestroyedCallbackForTesting)472     public void setOnDestroyedCallbackForTesting(Runnable onDestroyedCallbackForTesting) {
473         synchronized (mUrlRequestAdapterLock) {
474             mOnDestroyedCallbackForTesting = onDestroyedCallbackForTesting;
475         }
476     }
477 
setOnDestroyedUploadCallbackForTesting( Runnable onDestroyedUploadCallbackForTesting)478     public void setOnDestroyedUploadCallbackForTesting(
479             Runnable onDestroyedUploadCallbackForTesting) {
480         mUploadDataStream.setOnDestroyedCallbackForTesting(onDestroyedUploadCallbackForTesting);
481     }
482 
getUrlRequestAdapterForTesting()483     public long getUrlRequestAdapterForTesting() {
484         synchronized (mUrlRequestAdapterLock) {
485             return mUrlRequestAdapter;
486         }
487     }
488 
489     /**
490      * Posts task to application Executor. Used for Listener callbacks
491      * and other tasks that should not be executed on network thread.
492      */
postTaskToExecutor(Runnable task)493     private void postTaskToExecutor(Runnable task) {
494         try {
495             mExecutor.execute(task);
496         } catch (RejectedExecutionException failException) {
497             Log.e(
498                     CronetUrlRequestContext.LOG_TAG,
499                     "Exception posting task to executor",
500                     failException);
501             // If posting a task throws an exception, then we fail the request. This exception could
502             // be permanent (executor shutdown), transient (AbortPolicy, or CallerRunsPolicy with
503             // direct execution not permitted), or caused by the runnables we submit if
504             // mUserExecutor is a direct executor and direct execution is not permitted. In the
505             // latter two cases, there is at least have a chance to inform the embedder of the
506             // request's failure, since failWithException does not enforce that onFailed() is not
507             // executed inline.
508             failWithException(
509                     new CronetExceptionImpl("Exception posting task to executor", failException));
510         }
511     }
512 
convertRequestPriority(int priority)513     private static int convertRequestPriority(int priority) {
514         switch (priority) {
515             case Builder.REQUEST_PRIORITY_IDLE:
516                 return RequestPriority.IDLE;
517             case Builder.REQUEST_PRIORITY_LOWEST:
518                 return RequestPriority.LOWEST;
519             case Builder.REQUEST_PRIORITY_LOW:
520                 return RequestPriority.LOW;
521             case Builder.REQUEST_PRIORITY_MEDIUM:
522                 return RequestPriority.MEDIUM;
523             case Builder.REQUEST_PRIORITY_HIGHEST:
524                 return RequestPriority.HIGHEST;
525             default:
526                 return RequestPriority.MEDIUM;
527         }
528     }
529 
convertIdempotency(int idempotency)530     private static int convertIdempotency(int idempotency) {
531         switch (idempotency) {
532             case Builder.DEFAULT_IDEMPOTENCY:
533                 return Idempotency.DEFAULT_IDEMPOTENCY;
534             case Builder.IDEMPOTENT:
535                 return Idempotency.IDEMPOTENT;
536             case Builder.NOT_IDEMPOTENT:
537                 return Idempotency.NOT_IDEMPOTENT;
538             default:
539                 return Idempotency.DEFAULT_IDEMPOTENCY;
540         }
541     }
542 
543     /**
544      * Estimates the byte size of the headers in their on-wire format.
545      * We are not really interested in their specific size but something which is close enough.
546      */
547     @VisibleForTesting
estimateHeadersSizeInBytes(Map<String, List<String>> headers)548     public static long estimateHeadersSizeInBytes(Map<String, List<String>> headers) {
549         if (headers == null) return 0;
550 
551         long responseHeaderSizeInBytes = 0;
552         for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
553             String key = entry.getKey();
554             if (key != null) responseHeaderSizeInBytes += key.length();
555             if (entry.getValue() == null) continue;
556 
557             for (String content : entry.getValue()) {
558                 responseHeaderSizeInBytes += content.length();
559             }
560         }
561         return responseHeaderSizeInBytes;
562     }
563 
564     /**
565      * Estimates the byte size of the headers in their on-wire format.
566      * We are not really interested in their specific size but something which is close enough.
567      */
568     @VisibleForTesting
estimateHeadersSizeInBytes(HeadersList headers)569     public static long estimateHeadersSizeInBytes(HeadersList headers) {
570         if (headers == null) return 0;
571         long responseHeaderSizeInBytes = 0;
572         for (Map.Entry<String, String> entry : headers) {
573             String key = entry.getKey();
574             if (key != null) responseHeaderSizeInBytes += key.length();
575             String value = entry.getValue();
576             if (value != null) responseHeaderSizeInBytes += entry.getValue().length();
577         }
578         return responseHeaderSizeInBytes;
579     }
580 
prepareResponseInfoOnNetworkThread( int httpStatusCode, String httpStatusText, String[] headers, boolean wasCached, String negotiatedProtocol, String proxyServer, long receivedByteCount)581     private UrlResponseInfoImpl prepareResponseInfoOnNetworkThread(
582             int httpStatusCode,
583             String httpStatusText,
584             String[] headers,
585             boolean wasCached,
586             String negotiatedProtocol,
587             String proxyServer,
588             long receivedByteCount) {
589         HeadersList headersList = new HeadersList();
590         for (int i = 0; i < headers.length; i += 2) {
591             headersList.add(
592                     new AbstractMap.SimpleImmutableEntry<String, String>(
593                             headers[i], headers[i + 1]));
594         }
595         return new UrlResponseInfoImpl(
596                 new ArrayList<String>(mUrlChain),
597                 httpStatusCode,
598                 httpStatusText,
599                 headersList,
600                 wasCached,
601                 negotiatedProtocol,
602                 proxyServer,
603                 receivedByteCount);
604     }
605 
checkNotStarted()606     private void checkNotStarted() {
607         synchronized (mUrlRequestAdapterLock) {
608             if (mStarted || isDoneLocked()) {
609                 throw new IllegalStateException("Request is already started.");
610             }
611         }
612     }
613 
614     /**
615      * Helper method to set final status of CronetUrlRequest and clean up the
616      * native request adapter.
617      */
618     @GuardedBy("mUrlRequestAdapterLock")
destroyRequestAdapterLocked( @equestFinishedInfoImpl.FinishedReason int finishedReason)619     private void destroyRequestAdapterLocked(
620             @RequestFinishedInfoImpl.FinishedReason int finishedReason) {
621         assert mException == null || finishedReason == RequestFinishedInfo.FAILED;
622         mFinishedReason = finishedReason;
623         if (mUrlRequestAdapter == 0) {
624             return;
625         }
626         mRequestContext.onRequestDestroyed();
627         // Posts a task to destroy the native adapter.
628         CronetUrlRequestJni.get()
629                 .destroy(
630                         mUrlRequestAdapter,
631                         CronetUrlRequest.this,
632                         finishedReason == RequestFinishedInfo.CANCELED);
633         mUrlRequestAdapter = 0;
634     }
635 
636     /**
637      * If callback method throws an exception, request gets canceled
638      * and exception is reported via onFailed listener callback.
639      * Only called on the Executor.
640      */
onCallbackException(Exception e)641     private void onCallbackException(Exception e) {
642         CallbackException requestError =
643                 new CallbackExceptionImpl("Exception received from UrlRequest.Callback", e);
644         Log.e(CronetUrlRequestContext.LOG_TAG, "Exception in CalledByNative method", e);
645         failWithException(requestError);
646     }
647 
648     /** Called when UploadDataProvider encounters an error. */
onUploadException(Throwable e)649     void onUploadException(Throwable e) {
650         CallbackException uploadError =
651                 new CallbackExceptionImpl("Exception received from UploadDataProvider", e);
652         Log.e(CronetUrlRequestContext.LOG_TAG, "Exception in upload method", e);
653         failWithException(uploadError);
654     }
655 
656     /** Fails the request with an exception on any thread. */
failWithException(final CronetException exception)657     private void failWithException(final CronetException exception) {
658         synchronized (mUrlRequestAdapterLock) {
659             if (isDoneLocked()) {
660                 return;
661             }
662             assert mException == null;
663             mException = exception;
664             destroyRequestAdapterLocked(RequestFinishedInfo.FAILED);
665         }
666         // onFailed will be invoked from onNativeAdapterDestroyed() to ensure metrics collection.
667     }
668 
669     ////////////////////////////////////////////////
670     // Private methods called by the native code.
671     // Always called on network thread.
672     ////////////////////////////////////////////////
673 
674     /**
675      * Called before following redirects. The redirect will only be followed if
676      * {@link #followRedirect()} is called. If the redirect response has a body, it will be ignored.
677      * This will only be called between start and onResponseStarted.
678      *
679      * @param newLocation Location where request is redirected.
680      * @param httpStatusCode from redirect response
681      * @param receivedByteCount count of bytes received for redirect response
682      * @param headers an array of response headers with keys at the even indices
683      *         followed by the corresponding values at the odd indices.
684      */
685     @SuppressWarnings("unused")
686     @CalledByNative
onRedirectReceived( final String newLocation, int httpStatusCode, String httpStatusText, String[] headers, boolean wasCached, String negotiatedProtocol, String proxyServer, long receivedByteCount)687     private void onRedirectReceived(
688             final String newLocation,
689             int httpStatusCode,
690             String httpStatusText,
691             String[] headers,
692             boolean wasCached,
693             String negotiatedProtocol,
694             String proxyServer,
695             long receivedByteCount) {
696         final UrlResponseInfoImpl responseInfo =
697                 prepareResponseInfoOnNetworkThread(
698                         httpStatusCode,
699                         httpStatusText,
700                         headers,
701                         wasCached,
702                         negotiatedProtocol,
703                         proxyServer,
704                         receivedByteCount);
705 
706         // Have to do this after creating responseInfo.
707         mUrlChain.add(newLocation);
708 
709         Runnable task =
710                 new Runnable() {
711                     @Override
712                     public void run() {
713                         checkCallingThread();
714                         synchronized (mUrlRequestAdapterLock) {
715                             if (isDoneLocked()) {
716                                 return;
717                             }
718                             mWaitingOnRedirect = true;
719                         }
720 
721                         try {
722                             mCallback.onRedirectReceived(
723                                     CronetUrlRequest.this, responseInfo, newLocation);
724                         } catch (Exception e) {
725                             onCallbackException(e);
726                         }
727                     }
728                 };
729         postTaskToExecutor(task);
730     }
731 
732     /**
733      * Called when the final set of headers, after all redirects,
734      * is received. Can only be called once for each request.
735      */
736     @SuppressWarnings("unused")
737     @CalledByNative
onResponseStarted( int httpStatusCode, String httpStatusText, String[] headers, boolean wasCached, String negotiatedProtocol, String proxyServer, long receivedByteCount)738     private void onResponseStarted(
739             int httpStatusCode,
740             String httpStatusText,
741             String[] headers,
742             boolean wasCached,
743             String negotiatedProtocol,
744             String proxyServer,
745             long receivedByteCount) {
746         mResponseInfo =
747                 prepareResponseInfoOnNetworkThread(
748                         httpStatusCode,
749                         httpStatusText,
750                         headers,
751                         wasCached,
752                         negotiatedProtocol,
753                         proxyServer,
754                         receivedByteCount);
755         Runnable task =
756                 new Runnable() {
757                     @Override
758                     public void run() {
759                         checkCallingThread();
760                         synchronized (mUrlRequestAdapterLock) {
761                             if (isDoneLocked()) {
762                                 return;
763                             }
764                             mWaitingOnRead = true;
765                         }
766 
767                         try {
768                             mCallback.onResponseStarted(CronetUrlRequest.this, mResponseInfo);
769                         } catch (Exception e) {
770                             onCallbackException(e);
771                         }
772                     }
773                 };
774         postTaskToExecutor(task);
775     }
776 
777     /**
778      * Called whenever data is received. The ByteBuffer remains
779      * valid only until listener callback. Or if the callback
780      * pauses the request, it remains valid until the request is resumed.
781      * Cancelling the request also invalidates the buffer.
782      *
783      * @param byteBuffer ByteBuffer containing received data, starting at
784      *        initialPosition. Guaranteed to have at least one read byte. Its
785      *        limit has not yet been updated to reflect the bytes read.
786      * @param bytesRead Number of bytes read.
787      * @param initialPosition Original position of byteBuffer when passed to
788      *        read(). Used as a minimal check that the buffer hasn't been
789      *        modified while reading from the network.
790      * @param initialLimit Original limit of byteBuffer when passed to
791      *        read(). Used as a minimal check that the buffer hasn't been
792      *        modified while reading from the network.
793      * @param receivedByteCount number of bytes received.
794      */
795     @SuppressWarnings("unused")
796     @CalledByNative
onReadCompleted( final ByteBuffer byteBuffer, int bytesRead, int initialPosition, int initialLimit, long receivedByteCount)797     private void onReadCompleted(
798             final ByteBuffer byteBuffer,
799             int bytesRead,
800             int initialPosition,
801             int initialLimit,
802             long receivedByteCount) {
803         mResponseInfo.setReceivedByteCount(receivedByteCount);
804         if (byteBuffer.position() != initialPosition || byteBuffer.limit() != initialLimit) {
805             failWithException(
806                     new CronetExceptionImpl("ByteBuffer modified externally during read", null));
807             return;
808         }
809         if (mOnReadCompletedTask == null) {
810             mOnReadCompletedTask = new OnReadCompletedRunnable();
811         }
812         byteBuffer.position(initialPosition + bytesRead);
813         mOnReadCompletedTask.mByteBuffer = byteBuffer;
814         postTaskToExecutor(mOnReadCompletedTask);
815     }
816 
817     /**
818      * Called when request is completed successfully, no callbacks will be
819      * called afterwards.
820      *
821      * @param receivedByteCount number of bytes received.
822      */
823     @SuppressWarnings("unused")
824     @CalledByNative
onSucceeded(long receivedByteCount)825     private void onSucceeded(long receivedByteCount) {
826         mResponseInfo.setReceivedByteCount(receivedByteCount);
827         Runnable task =
828                 new Runnable() {
829                     @Override
830                     public void run() {
831                         synchronized (mUrlRequestAdapterLock) {
832                             if (isDoneLocked()) {
833                                 return;
834                             }
835                             // Destroy adapter first, so request context could be shut
836                             // down from the listener.
837                             destroyRequestAdapterLocked(RequestFinishedInfo.SUCCEEDED);
838                         }
839                         try {
840                             mCallback.onSucceeded(CronetUrlRequest.this, mResponseInfo);
841                         } catch (Exception e) {
842                             Log.e(
843                                     CronetUrlRequestContext.LOG_TAG,
844                                     "Exception in onSucceeded method",
845                                     e);
846                         }
847                         maybeReportMetrics();
848                     }
849                 };
850         postTaskToExecutor(task);
851     }
852 
853     /**
854      * Called when error has occurred, no callbacks will be called afterwards.
855      *
856      * @param errorCode Error code represented by {@code UrlRequestError} that should be mapped
857      *                  to one of {@link NetworkException#ERROR_HOSTNAME_NOT_RESOLVED
858      *                  NetworkException.ERROR_*}.
859      * @param nativeError native net error code.
860      * @param errorString textual representation of the error code.
861      * @param receivedByteCount number of bytes received.
862      */
863     @SuppressWarnings("unused")
864     @CalledByNative
onError( int errorCode, int nativeError, int nativeQuicError, String errorString, long receivedByteCount)865     private void onError(
866             int errorCode,
867             int nativeError,
868             int nativeQuicError,
869             String errorString,
870             long receivedByteCount) {
871         if (mResponseInfo != null) {
872             mResponseInfo.setReceivedByteCount(receivedByteCount);
873         }
874         if (errorCode == NetworkException.ERROR_QUIC_PROTOCOL_FAILED
875                 || errorCode == NetworkException.ERROR_NETWORK_CHANGED) {
876             failWithException(
877                     new QuicExceptionImpl(
878                             "Exception in CronetUrlRequest: " + errorString,
879                             errorCode,
880                             nativeError,
881                             nativeQuicError));
882         } else {
883             int javaError = mapUrlRequestErrorToApiErrorCode(errorCode);
884             failWithException(
885                     new NetworkExceptionImpl(
886                             "Exception in CronetUrlRequest: " + errorString,
887                             javaError,
888                             nativeError));
889         }
890     }
891 
892     /** Called when request is canceled, no callbacks will be called afterwards. */
893     @SuppressWarnings("unused")
894     @CalledByNative
onCanceled()895     private void onCanceled() {
896         Runnable task =
897                 new Runnable() {
898                     @Override
899                     public void run() {
900                         try {
901                             mCallback.onCanceled(CronetUrlRequest.this, mResponseInfo);
902                         } catch (Exception e) {
903                             Log.e(
904                                     CronetUrlRequestContext.LOG_TAG,
905                                     "Exception in onCanceled method",
906                                     e);
907                         }
908                         maybeReportMetrics();
909                     }
910                 };
911         postTaskToExecutor(task);
912     }
913 
914     /**
915      * Called by the native code when request status is fetched from the
916      * native stack.
917      */
918     @SuppressWarnings("unused")
919     @CalledByNative
onStatus( final VersionSafeCallbacks.UrlRequestStatusListener listener, final int loadState)920     private void onStatus(
921             final VersionSafeCallbacks.UrlRequestStatusListener listener, final int loadState) {
922         Runnable task =
923                 new Runnable() {
924                     @Override
925                     public void run() {
926                         listener.onStatus(convertLoadState(loadState));
927                     }
928                 };
929         postTaskToExecutor(task);
930     }
931 
932     /**
933      * Called by the native code on the network thread to report metrics. Happens before
934      * onSucceeded, onError and onCanceled.
935      */
936     @SuppressWarnings("unused")
937     @CalledByNative
onMetricsCollected( long requestStartMs, long dnsStartMs, long dnsEndMs, long connectStartMs, long connectEndMs, long sslStartMs, long sslEndMs, long sendingStartMs, long sendingEndMs, long pushStartMs, long pushEndMs, long responseStartMs, long requestEndMs, boolean socketReused, long sentByteCount, long receivedByteCount, boolean quicConnectionMigrationAttempted, boolean quicConnectionMigrationSuccessful)938     private void onMetricsCollected(
939             long requestStartMs,
940             long dnsStartMs,
941             long dnsEndMs,
942             long connectStartMs,
943             long connectEndMs,
944             long sslStartMs,
945             long sslEndMs,
946             long sendingStartMs,
947             long sendingEndMs,
948             long pushStartMs,
949             long pushEndMs,
950             long responseStartMs,
951             long requestEndMs,
952             boolean socketReused,
953             long sentByteCount,
954             long receivedByteCount,
955             boolean quicConnectionMigrationAttempted,
956             boolean quicConnectionMigrationSuccessful) {
957         synchronized (mUrlRequestAdapterLock) {
958             if (mMetrics != null) {
959                 throw new IllegalStateException("Metrics collection should only happen once.");
960             }
961             mMetrics =
962                     new CronetMetrics(
963                             requestStartMs,
964                             dnsStartMs,
965                             dnsEndMs,
966                             connectStartMs,
967                             connectEndMs,
968                             sslStartMs,
969                             sslEndMs,
970                             sendingStartMs,
971                             sendingEndMs,
972                             pushStartMs,
973                             pushEndMs,
974                             responseStartMs,
975                             requestEndMs,
976                             socketReused,
977                             sentByteCount,
978                             receivedByteCount);
979             mQuicConnectionMigrationAttempted = quicConnectionMigrationAttempted;
980             mQuicConnectionMigrationSuccessful = quicConnectionMigrationSuccessful;
981         }
982         // Metrics are reported to RequestFinishedListener when the final UrlRequest.Callback has
983         // been invoked.
984     }
985 
986     /** Called when the native adapter is destroyed. */
987     @SuppressWarnings("unused")
988     @CalledByNative
onNativeAdapterDestroyed()989     private void onNativeAdapterDestroyed() {
990         synchronized (mUrlRequestAdapterLock) {
991             if (mOnDestroyedCallbackForTesting != null) {
992                 mOnDestroyedCallbackForTesting.run();
993             }
994             // mException is set when an error is encountered (in native code via onError or in
995             // Java code). If mException is not null, notify the mCallback and report metrics.
996             if (mException == null) {
997                 return;
998             }
999         }
1000         Runnable task =
1001                 new Runnable() {
1002                     @Override
1003                     public void run() {
1004                         try {
1005                             mCallback.onFailed(CronetUrlRequest.this, mResponseInfo, mException);
1006                         } catch (Exception e) {
1007                             Log.e(
1008                                     CronetUrlRequestContext.LOG_TAG,
1009                                     "Exception in onFailed method",
1010                                     e);
1011                         }
1012                         maybeReportMetrics();
1013                     }
1014                 };
1015         try {
1016             mExecutor.execute(task);
1017         } catch (RejectedExecutionException e) {
1018             Log.e(CronetUrlRequestContext.LOG_TAG, "Exception posting task to executor", e);
1019         }
1020     }
1021 
1022     /** Enforces prohibition of direct execution. */
checkCallingThread()1023     void checkCallingThread() {
1024         if (!mAllowDirectExecutor && mRequestContext.isNetworkThread(Thread.currentThread())) {
1025             throw new InlineExecutionProhibitedException();
1026         }
1027     }
1028 
mapUrlRequestErrorToApiErrorCode(int errorCode)1029     private int mapUrlRequestErrorToApiErrorCode(int errorCode) {
1030         switch (errorCode) {
1031             case UrlRequestError.HOSTNAME_NOT_RESOLVED:
1032                 return NetworkException.ERROR_HOSTNAME_NOT_RESOLVED;
1033             case UrlRequestError.INTERNET_DISCONNECTED:
1034                 return NetworkException.ERROR_INTERNET_DISCONNECTED;
1035             case UrlRequestError.NETWORK_CHANGED:
1036                 return NetworkException.ERROR_NETWORK_CHANGED;
1037             case UrlRequestError.TIMED_OUT:
1038                 return NetworkException.ERROR_TIMED_OUT;
1039             case UrlRequestError.CONNECTION_CLOSED:
1040                 return NetworkException.ERROR_CONNECTION_CLOSED;
1041             case UrlRequestError.CONNECTION_TIMED_OUT:
1042                 return NetworkException.ERROR_CONNECTION_TIMED_OUT;
1043             case UrlRequestError.CONNECTION_REFUSED:
1044                 return NetworkException.ERROR_CONNECTION_REFUSED;
1045             case UrlRequestError.CONNECTION_RESET:
1046                 return NetworkException.ERROR_CONNECTION_RESET;
1047             case UrlRequestError.ADDRESS_UNREACHABLE:
1048                 return NetworkException.ERROR_ADDRESS_UNREACHABLE;
1049             case UrlRequestError.QUIC_PROTOCOL_FAILED:
1050                 return NetworkException.ERROR_QUIC_PROTOCOL_FAILED;
1051             case UrlRequestError.OTHER:
1052                 return NetworkException.ERROR_OTHER;
1053             default:
1054                 Log.e(CronetUrlRequestContext.LOG_TAG, "Unknown error code: " + errorCode);
1055                 return errorCode;
1056         }
1057     }
1058 
1059     /**
1060      * Builds the {@link CronetTrafficInfo} associated to this request internal state.
1061      * This helper methods makes strong assumptions about the state of the request. For this reason
1062      * it should only be called within {@link CronetUrlRequest#maybeReportMetrics} where these
1063      * assumptions are guaranteed to be true.
1064      * @return the {@link CronetTrafficInfo} associated to this request internal state
1065      */
1066     @RequiresApi(Build.VERSION_CODES.O)
buildCronetTrafficInfo()1067     private CronetTrafficInfo buildCronetTrafficInfo() {
1068         assert mMetrics != null;
1069         assert mRequestHeaders != null;
1070 
1071         // Most of the CronetTrafficInfo fields have similar names/semantics. To avoid bugs due to
1072         // typos everything is final, this means that things have to initialized through an if/else.
1073         final Map<String, List<String>> responseHeaders;
1074         final String negotiatedProtocol;
1075         final int httpStatusCode;
1076         final boolean wasCached;
1077         if (mResponseInfo != null) {
1078             responseHeaders = mResponseInfo.getAllHeaders();
1079             negotiatedProtocol = mResponseInfo.getNegotiatedProtocol();
1080             httpStatusCode = mResponseInfo.getHttpStatusCode();
1081             wasCached = mResponseInfo.wasCached();
1082         } else {
1083             responseHeaders = Collections.emptyMap();
1084             negotiatedProtocol = "";
1085             httpStatusCode = 0;
1086             wasCached = false;
1087         }
1088 
1089         // TODO(stefanoduo): A better approach might be keeping track of the total length of an
1090         // upload and use that value as the request body size instead.
1091         final long requestTotalSizeInBytes = mMetrics.getSentByteCount();
1092         final long requestHeaderSizeInBytes;
1093         final long requestBodySizeInBytes;
1094         // Cached responses might still need to be revalidated over the network before being served
1095         // (from UrlResponseInfo#wasCached documentation).
1096         if (wasCached && requestTotalSizeInBytes == 0) {
1097             // Served from cache without the need to revalidate.
1098             requestHeaderSizeInBytes = 0;
1099             requestBodySizeInBytes = 0;
1100         } else {
1101             // Served from cache with the need to revalidate or served from the network directly.
1102             requestHeaderSizeInBytes = estimateHeadersSizeInBytes(mRequestHeaders);
1103             requestBodySizeInBytes = max(0, requestTotalSizeInBytes - requestHeaderSizeInBytes);
1104         }
1105 
1106         final long responseTotalSizeInBytes = mMetrics.getReceivedByteCount();
1107         final long responseBodySizeInBytes;
1108         final long responseHeaderSizeInBytes;
1109         // Cached responses might still need to be revalidated over the network before being served
1110         // (from UrlResponseInfo#wasCached documentation).
1111         if (wasCached && responseTotalSizeInBytes == 0) {
1112             // Served from cache without the need to revalidate.
1113             responseBodySizeInBytes = 0;
1114             responseHeaderSizeInBytes = 0;
1115         } else {
1116             // Served from cache with the need to revalidate or served from the network directly.
1117             responseHeaderSizeInBytes = estimateHeadersSizeInBytes(responseHeaders);
1118             responseBodySizeInBytes = max(0, responseTotalSizeInBytes - responseHeaderSizeInBytes);
1119         }
1120 
1121         final Duration headersLatency;
1122         if (mMetrics.getRequestStart() != null && mMetrics.getResponseStart() != null) {
1123             headersLatency =
1124                     Duration.ofMillis(
1125                             mMetrics.getResponseStart().getTime()
1126                                     - mMetrics.getRequestStart().getTime());
1127         } else {
1128             headersLatency = Duration.ofSeconds(0);
1129         }
1130 
1131         final Duration totalLatency;
1132         if (mMetrics.getRequestStart() != null && mMetrics.getRequestEnd() != null) {
1133             totalLatency =
1134                     Duration.ofMillis(
1135                             mMetrics.getRequestEnd().getTime()
1136                                     - mMetrics.getRequestStart().getTime());
1137         } else {
1138             totalLatency = Duration.ofSeconds(0);
1139         }
1140 
1141         return new CronetTrafficInfo(
1142                 requestHeaderSizeInBytes,
1143                 requestBodySizeInBytes,
1144                 responseHeaderSizeInBytes,
1145                 responseBodySizeInBytes,
1146                 httpStatusCode,
1147                 headersLatency,
1148                 totalLatency,
1149                 negotiatedProtocol,
1150                 mQuicConnectionMigrationAttempted,
1151                 mQuicConnectionMigrationSuccessful);
1152     }
1153 
1154     // Maybe report metrics. This method should only be called on Callback's executor thread and
1155     // after Callback's onSucceeded, onFailed and onCanceled.
maybeReportMetrics()1156     private void maybeReportMetrics() {
1157         final RefCountDelegate inflightCallbackCount =
1158                 new RefCountDelegate(() -> mRequestContext.onRequestFinished());
1159         try {
1160             if (mMetrics == null) return;
1161 
1162             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
1163                 try {
1164                     mLogger.logCronetTrafficInfo(mCronetEngineId, buildCronetTrafficInfo());
1165                 } catch (RuntimeException e) {
1166                     // Handle any issue gracefully, we should never crash due failures while
1167                     // logging.
1168                     Log.e(
1169                             CronetUrlRequestContext.LOG_TAG,
1170                             "Error while trying to log CronetTrafficInfo: ",
1171                             e);
1172                 }
1173             }
1174 
1175             final RequestFinishedInfo requestInfo =
1176                     new RequestFinishedInfoImpl(
1177                             mInitialUrl,
1178                             mRequestAnnotations,
1179                             mMetrics,
1180                             mFinishedReason,
1181                             mResponseInfo,
1182                             mException);
1183             mRequestContext.reportRequestFinished(requestInfo, inflightCallbackCount);
1184             if (mRequestFinishedListener != null) {
1185                 inflightCallbackCount.increment();
1186                 try {
1187                     mRequestFinishedListener
1188                             .getExecutor()
1189                             .execute(
1190                                     new Runnable() {
1191                                         @Override
1192                                         public void run() {
1193                                             try {
1194                                                 mRequestFinishedListener.onRequestFinished(
1195                                                         requestInfo);
1196                                             } catch (Exception e) {
1197                                                 Log.e(
1198                                                         CronetUrlRequestContext.LOG_TAG,
1199                                                         "Exception thrown from request"
1200                                                                 + " finishedlistener",
1201                                                         e);
1202                                             } finally {
1203                                                 inflightCallbackCount.decrement();
1204                                             }
1205                                         }
1206                                     });
1207                 } catch (RejectedExecutionException failException) {
1208                     Log.e(
1209                             CronetUrlRequestContext.LOG_TAG,
1210                             "Exception posting task to executor",
1211                             failException);
1212                     inflightCallbackCount.decrement();
1213                 }
1214             }
1215         } finally {
1216             inflightCallbackCount.decrement();
1217         }
1218     }
1219 
1220     // Native methods are implemented in cronet_url_request_adapter.cc.
1221     @NativeMethods
1222     interface Natives {
createRequestAdapter( CronetUrlRequest caller, long urlRequestContextAdapter, String url, int priority, boolean disableCache, boolean disableConnectionMigration, boolean trafficStatsTagSet, int trafficStatsTag, boolean trafficStatsUidSet, int trafficStatsUid, int idempotency, long networkHandle)1223         long createRequestAdapter(
1224                 CronetUrlRequest caller,
1225                 long urlRequestContextAdapter,
1226                 String url,
1227                 int priority,
1228                 boolean disableCache,
1229                 boolean disableConnectionMigration,
1230                 boolean trafficStatsTagSet,
1231                 int trafficStatsTag,
1232                 boolean trafficStatsUidSet,
1233                 int trafficStatsUid,
1234                 int idempotency,
1235                 long networkHandle);
1236 
1237         @NativeClassQualifiedName("CronetURLRequestAdapter")
setHttpMethod(long nativePtr, CronetUrlRequest caller, String method)1238         boolean setHttpMethod(long nativePtr, CronetUrlRequest caller, String method);
1239 
1240         @NativeClassQualifiedName("CronetURLRequestAdapter")
addRequestHeader( long nativePtr, CronetUrlRequest caller, String name, String value)1241         boolean addRequestHeader(
1242                 long nativePtr, CronetUrlRequest caller, String name, String value);
1243 
1244         @NativeClassQualifiedName("CronetURLRequestAdapter")
start(long nativePtr, CronetUrlRequest caller)1245         void start(long nativePtr, CronetUrlRequest caller);
1246 
1247         @NativeClassQualifiedName("CronetURLRequestAdapter")
followDeferredRedirect(long nativePtr, CronetUrlRequest caller)1248         void followDeferredRedirect(long nativePtr, CronetUrlRequest caller);
1249 
1250         @NativeClassQualifiedName("CronetURLRequestAdapter")
readData( long nativePtr, CronetUrlRequest caller, ByteBuffer byteBuffer, int position, int capacity)1251         boolean readData(
1252                 long nativePtr,
1253                 CronetUrlRequest caller,
1254                 ByteBuffer byteBuffer,
1255                 int position,
1256                 int capacity);
1257 
1258         @NativeClassQualifiedName("CronetURLRequestAdapter")
destroy(long nativePtr, CronetUrlRequest caller, boolean sendOnCanceled)1259         void destroy(long nativePtr, CronetUrlRequest caller, boolean sendOnCanceled);
1260 
1261         @NativeClassQualifiedName("CronetURLRequestAdapter")
getStatus( long nativePtr, CronetUrlRequest caller, VersionSafeCallbacks.UrlRequestStatusListener listener)1262         void getStatus(
1263                 long nativePtr,
1264                 CronetUrlRequest caller,
1265                 VersionSafeCallbacks.UrlRequestStatusListener listener);
1266     }
1267 }
1268