• 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 org.chromium.net.ApiVersion;
8 import android.os.Build;
9 import android.os.ConditionVariable;
10 import android.os.Process;
11 
12 import androidx.annotation.VisibleForTesting;
13 
14 import org.jni_zero.CalledByNative;
15 import org.jni_zero.JNINamespace;
16 import org.jni_zero.NativeClassQualifiedName;
17 import org.jni_zero.NativeMethods;
18 
19 import org.chromium.base.Log;
20 import org.chromium.base.ObserverList;
21 import org.chromium.build.annotations.UsedByReflection;
22 import org.chromium.net.BidirectionalStream;
23 import org.chromium.net.CronetEngine;
24 import org.chromium.net.EffectiveConnectionType;
25 import org.chromium.net.ExperimentalBidirectionalStream;
26 import org.chromium.net.NetworkQualityRttListener;
27 import org.chromium.net.NetworkQualityThroughputListener;
28 import org.chromium.net.RequestContextConfigOptions;
29 import org.chromium.net.RequestFinishedInfo;
30 import org.chromium.net.RttThroughputValues;
31 import org.chromium.net.UrlRequest;
32 import org.chromium.net.impl.CronetLogger.CronetEngineBuilderInfo;
33 import org.chromium.net.impl.CronetLogger.CronetSource;
34 import org.chromium.net.impl.CronetLogger.CronetVersion;
35 import org.chromium.net.urlconnection.CronetHttpURLConnection;
36 import org.chromium.net.urlconnection.CronetURLStreamHandlerFactory;
37 
38 import java.net.Proxy;
39 import java.net.URL;
40 import java.net.URLConnection;
41 import java.net.URLStreamHandlerFactory;
42 import java.util.ArrayList;
43 import java.util.Collection;
44 import java.util.HashMap;
45 import java.util.HashSet;
46 import java.util.List;
47 import java.util.Map;
48 import java.util.concurrent.Executor;
49 import java.util.concurrent.RejectedExecutionException;
50 import java.util.concurrent.atomic.AtomicInteger;
51 
52 import javax.annotation.concurrent.GuardedBy;
53 
54 /** CronetEngine using Chromium HTTP stack implementation. */
55 @JNINamespace("cronet")
56 @UsedByReflection("CronetEngine.java")
57 @VisibleForTesting
58 public class CronetUrlRequestContext extends CronetEngineBase {
59     static final String LOG_TAG = CronetUrlRequestContext.class.getSimpleName();
60 
61     /** Synchronize access to mUrlRequestContextAdapter and shutdown routine. */
62     private final Object mLock = new Object();
63 
64     private final ConditionVariable mInitCompleted = new ConditionVariable(false);
65 
66     /**
67      * The number of started requests where the terminal callback (i.e.
68      * onSucceeded/onCancelled/onFailed) has not yet been called.
69      */
70     private final AtomicInteger mRunningRequestCount = new AtomicInteger(0);
71 
72     /*
73      * The number of started requests where the terminal callbacks (i.e.
74      * onSucceeded/onCancelled/onFailed, request finished listeners) have not
75      * all returned yet.
76      *
77      * By definition this is always greater than or equal to
78      * mRunningRequestCount. The difference between the two is the number of
79      * terminal callbacks that are currently running.
80      */
81     private final AtomicInteger mActiveRequestCount = new AtomicInteger(0);
82 
83     @GuardedBy("mLock")
84     private long mUrlRequestContextAdapter;
85 
86     /**
87      * This field is accessed without synchronization, but only for the purposes of reference
88      * equality comparison with other threads. If such a comparison is performed on the network
89      * thread, then there is a happens-before edge between the write of this field and the
90      * subsequent read; if it's performed on another thread, then observing a value of null won't
91      * change the result of the comparison.
92      */
93     private Thread mNetworkThread;
94 
95     private final boolean mNetworkQualityEstimatorEnabled;
96 
97     /**
98      * Locks operations on network quality listeners, because listener
99      * addition and removal may occur on a different thread from notification.
100      */
101     private final Object mNetworkQualityLock = new Object();
102 
103     /**
104      * Locks operations on the list of RequestFinishedInfo.Listeners, because operations can happen
105      * on any thread. This should be used for fine-grained locking only. In particular, don't call
106      * any UrlRequest methods that acquire mUrlRequestAdapterLock while holding this lock.
107      */
108     private final Object mFinishedListenerLock = new Object();
109 
110     /**
111      * Current effective connection type as computed by the network quality
112      * estimator.
113      */
114     @GuardedBy("mNetworkQualityLock")
115     private int mEffectiveConnectionType = EffectiveConnectionType.TYPE_UNKNOWN;
116 
117     /**
118      * Current estimate of the HTTP RTT (in milliseconds) computed by the
119      * network quality estimator.
120      */
121     @GuardedBy("mNetworkQualityLock")
122     private int mHttpRttMs = RttThroughputValues.INVALID_RTT_THROUGHPUT;
123 
124     /**
125      * Current estimate of the transport RTT (in milliseconds) computed by the
126      * network quality estimator.
127      */
128     @GuardedBy("mNetworkQualityLock")
129     private int mTransportRttMs = RttThroughputValues.INVALID_RTT_THROUGHPUT;
130 
131     /**
132      * Current estimate of the downstream throughput (in kilobits per second)
133      * computed by the network quality estimator.
134      */
135     @GuardedBy("mNetworkQualityLock")
136     private int mDownstreamThroughputKbps = RttThroughputValues.INVALID_RTT_THROUGHPUT;
137 
138     @GuardedBy("mNetworkQualityLock")
139     private final ObserverList<VersionSafeCallbacks.NetworkQualityRttListenerWrapper>
140             mRttListenerList =
141                     new ObserverList<VersionSafeCallbacks.NetworkQualityRttListenerWrapper>();
142 
143     @GuardedBy("mNetworkQualityLock")
144     private final ObserverList<VersionSafeCallbacks.NetworkQualityThroughputListenerWrapper>
145             mThroughputListenerList =
146                     new ObserverList<
147                             VersionSafeCallbacks.NetworkQualityThroughputListenerWrapper>();
148 
149     @GuardedBy("mFinishedListenerLock")
150     private final Map<
151                     RequestFinishedInfo.Listener, VersionSafeCallbacks.RequestFinishedInfoListener>
152             mFinishedListenerMap =
153                     new HashMap<
154                             RequestFinishedInfo.Listener,
155                             VersionSafeCallbacks.RequestFinishedInfoListener>();
156 
157     private final ConditionVariable mStopNetLogCompleted = new ConditionVariable();
158 
159     /** Set of storage paths currently in use. */
160     @GuardedBy("sInUseStoragePaths")
161     private static final HashSet<String> sInUseStoragePaths = new HashSet<String>();
162 
163     /** Storage path used by this context. */
164     private final String mInUseStoragePath;
165 
166     /** True if a NetLog observer is active. */
167     @GuardedBy("mLock")
168     private boolean mIsLogging;
169 
170     /** True if NetLog is being shutdown. */
171     @GuardedBy("mLock")
172     private boolean mIsStoppingNetLog;
173 
174     /** The network handle to be used for requests that do not explicitly specify one. **/
175     private long mNetworkHandle = DEFAULT_NETWORK_HANDLE;
176 
177     private final int mCronetEngineId;
178 
179     /** Whether Cronet Telemetry should be enabled or not. */
180     private final boolean mEnableTelemetry;
181 
182     /** The logger to be used for logging. */
183     private final CronetLogger mLogger;
184 
getCronetEngineId()185     int getCronetEngineId() {
186         return mCronetEngineId;
187     }
188 
getCronetLogger()189     CronetLogger getCronetLogger() {
190         return mLogger;
191     }
192 
getEnableTelemetryForTesting()193     public boolean getEnableTelemetryForTesting() {
194         return mEnableTelemetry;
195     }
196 
197     @UsedByReflection("CronetEngine.java")
CronetUrlRequestContext(final CronetEngineBuilderImpl builder)198     public CronetUrlRequestContext(final CronetEngineBuilderImpl builder) {
199         mCronetEngineId = hashCode();
200         mRttListenerList.disableThreadAsserts();
201         mThroughputListenerList.disableThreadAsserts();
202         mNetworkQualityEstimatorEnabled = builder.networkQualityEstimatorEnabled();
203         CronetLibraryLoader.ensureInitialized(builder.getContext(), builder);
204         if (builder.httpCacheMode() == HttpCacheType.DISK) {
205             mInUseStoragePath = builder.storagePath();
206             synchronized (sInUseStoragePaths) {
207                 if (!sInUseStoragePaths.add(mInUseStoragePath)) {
208                     throw new IllegalStateException("Disk cache storage path already in use");
209                 }
210             }
211         } else {
212             mInUseStoragePath = null;
213         }
214         synchronized (mLock) {
215             mUrlRequestContextAdapter =
216                     CronetUrlRequestContextJni.get()
217                             .createRequestContextAdapter(
218                                     createNativeUrlRequestContextConfig(builder));
219             if (mUrlRequestContextAdapter == 0) {
220                 throw new NullPointerException("Context Adapter creation failed.");
221             }
222             mEnableTelemetry =
223                     CronetUrlRequestContextJni.get()
224                             .getEnableTelemetry(
225                                     mUrlRequestContextAdapter, CronetUrlRequestContext.this);
226         }
227 
228         if (mEnableTelemetry) {
229             mLogger = CronetLoggerFactory.createLogger(builder.getContext(), getCronetSource());
230         } else {
231             mLogger = CronetLoggerFactory.createNoOpLogger();
232         }
233         try {
234             mLogger.logCronetEngineCreation(
235                     getCronetEngineId(),
236                     new CronetEngineBuilderInfo(builder),
237                     buildCronetVersion(),
238                     getCronetSource());
239         } catch (RuntimeException e) {
240             // Handle any issue gracefully, we should never crash due failures while logging.
241             Log.e(LOG_TAG, "Error while trying to log CronetEngine creation: ", e);
242         }
243 
244         // Init native Chromium URLRequestContext on init thread.
245         CronetLibraryLoader.postToInitThread(
246                 new Runnable() {
247                     @Override
248                     public void run() {
249                         CronetLibraryLoader.ensureInitializedOnInitThread();
250                         synchronized (mLock) {
251                             // mUrlRequestContextAdapter is guaranteed to exist until
252                             // initialization on init and network threads completes and
253                             // initNetworkThread is called back on network thread.
254                             CronetUrlRequestContextJni.get()
255                                     .initRequestContextOnInitThread(
256                                             mUrlRequestContextAdapter,
257                                             CronetUrlRequestContext.this);
258                         }
259                     }
260                 });
261     }
262 
getCronetSource()263     static CronetSource getCronetSource() {
264         ClassLoader implClassLoader = CronetUrlRequest.class.getClassLoader();
265         if (implClassLoader.toString().startsWith("java.lang.BootClassLoader")) {
266             return CronetSource.CRONET_SOURCE_PLATFORM;
267         }
268         ClassLoader apiClassLoader = CronetEngine.class.getClassLoader();
269         return apiClassLoader.equals(implClassLoader)
270                 ? CronetSource.CRONET_SOURCE_STATICALLY_LINKED
271                 : CronetSource.CRONET_SOURCE_PLAY_SERVICES;
272     }
273 
274     @VisibleForTesting
createNativeUrlRequestContextConfig(CronetEngineBuilderImpl builder)275     public static long createNativeUrlRequestContextConfig(CronetEngineBuilderImpl builder) {
276         final long urlRequestContextConfig =
277                 CronetUrlRequestContextJni.get()
278                         .createRequestContextConfig(
279                                 createRequestContextConfigOptions(builder).toByteArray());
280         if (urlRequestContextConfig == 0) {
281             throw new IllegalArgumentException("Experimental options parsing failed.");
282         }
283         for (CronetEngineBuilderImpl.QuicHint quicHint : builder.quicHints()) {
284             CronetUrlRequestContextJni.get()
285                     .addQuicHint(
286                             urlRequestContextConfig,
287                             quicHint.mHost,
288                             quicHint.mPort,
289                             quicHint.mAlternatePort);
290         }
291         for (CronetEngineBuilderImpl.Pkp pkp : builder.publicKeyPins()) {
292             CronetUrlRequestContextJni.get()
293                     .addPkp(
294                             urlRequestContextConfig,
295                             pkp.mHost,
296                             pkp.mHashes,
297                             pkp.mIncludeSubdomains,
298                             pkp.mExpirationDate.getTime());
299         }
300         return urlRequestContextConfig;
301     }
302 
createRequestContextConfigOptions( CronetEngineBuilderImpl engineBuilder)303     private static RequestContextConfigOptions createRequestContextConfigOptions(
304             CronetEngineBuilderImpl engineBuilder) {
305         RequestContextConfigOptions.Builder resultBuilder =
306                 RequestContextConfigOptions.newBuilder()
307                         .setQuicEnabled(engineBuilder.quicEnabled())
308                         .setHttp2Enabled(engineBuilder.http2Enabled())
309                         .setBrotliEnabled(engineBuilder.brotliEnabled())
310                         .setDisableCache(engineBuilder.cacheDisabled())
311                         .setHttpCacheMode(engineBuilder.httpCacheMode())
312                         .setHttpCacheMaxSize(engineBuilder.httpCacheMaxSize())
313                         .setMockCertVerifier(engineBuilder.mockCertVerifier())
314                         .setEnableNetworkQualityEstimator(
315                                 engineBuilder.networkQualityEstimatorEnabled())
316                         .setBypassPublicKeyPinningForLocalTrustAnchors(
317                                 engineBuilder.publicKeyPinningBypassForLocalTrustAnchorsEnabled())
318                         .setNetworkThreadPriority(
319                                 engineBuilder.threadPriority(Process.THREAD_PRIORITY_BACKGROUND));
320 
321         if (engineBuilder.getUserAgent() != null) {
322             resultBuilder.setUserAgent(engineBuilder.getUserAgent());
323         }
324 
325         if (engineBuilder.storagePath() != null) {
326             resultBuilder.setStoragePath(engineBuilder.storagePath());
327         }
328 
329         if (engineBuilder.getDefaultQuicUserAgentId() != null) {
330             resultBuilder.setQuicDefaultUserAgentId(engineBuilder.getDefaultQuicUserAgentId());
331         }
332 
333         if (engineBuilder.experimentalOptions() != null) {
334             resultBuilder.setExperimentalOptions(engineBuilder.experimentalOptions());
335         }
336 
337         return resultBuilder.build();
338     }
339 
340     @Override
newBidirectionalStreamBuilder( String url, BidirectionalStream.Callback callback, Executor executor)341     public ExperimentalBidirectionalStream.Builder newBidirectionalStreamBuilder(
342             String url, BidirectionalStream.Callback callback, Executor executor) {
343         return new BidirectionalStreamBuilderImpl(url, callback, executor, this);
344     }
345 
346     @Override
createRequest( String url, UrlRequest.Callback callback, Executor executor, int priority, Collection<Object> requestAnnotations, boolean disableCache, boolean disableConnectionMigration, boolean allowDirectExecutor, boolean trafficStatsTagSet, int trafficStatsTag, boolean trafficStatsUidSet, int trafficStatsUid, RequestFinishedInfo.Listener requestFinishedListener, int idempotency, long networkHandle)347     public UrlRequestBase createRequest(
348             String url,
349             UrlRequest.Callback callback,
350             Executor executor,
351             int priority,
352             Collection<Object> requestAnnotations,
353             boolean disableCache,
354             boolean disableConnectionMigration,
355             boolean allowDirectExecutor,
356             boolean trafficStatsTagSet,
357             int trafficStatsTag,
358             boolean trafficStatsUidSet,
359             int trafficStatsUid,
360             RequestFinishedInfo.Listener requestFinishedListener,
361             int idempotency,
362             long networkHandle) {
363         // if this request is not bound to network, use the network bound to the engine.
364         if (networkHandle == DEFAULT_NETWORK_HANDLE) {
365             networkHandle = mNetworkHandle;
366         }
367         synchronized (mLock) {
368             checkHaveAdapter();
369             return new CronetUrlRequest(
370                     this,
371                     url,
372                     priority,
373                     callback,
374                     executor,
375                     requestAnnotations,
376                     disableCache,
377                     disableConnectionMigration,
378                     allowDirectExecutor,
379                     trafficStatsTagSet,
380                     trafficStatsTag,
381                     trafficStatsUidSet,
382                     trafficStatsUid,
383                     requestFinishedListener,
384                     idempotency,
385                     networkHandle);
386         }
387     }
388 
389     @Override
createBidirectionalStream( String url, BidirectionalStream.Callback callback, Executor executor, String httpMethod, List<Map.Entry<String, String>> requestHeaders, @StreamPriority int priority, boolean delayRequestHeadersUntilFirstFlush, Collection<Object> requestAnnotations, boolean trafficStatsTagSet, int trafficStatsTag, boolean trafficStatsUidSet, int trafficStatsUid, long networkHandle)390     protected ExperimentalBidirectionalStream createBidirectionalStream(
391             String url,
392             BidirectionalStream.Callback callback,
393             Executor executor,
394             String httpMethod,
395             List<Map.Entry<String, String>> requestHeaders,
396             @StreamPriority int priority,
397             boolean delayRequestHeadersUntilFirstFlush,
398             Collection<Object> requestAnnotations,
399             boolean trafficStatsTagSet,
400             int trafficStatsTag,
401             boolean trafficStatsUidSet,
402             int trafficStatsUid,
403             long networkHandle) {
404         if (networkHandle == DEFAULT_NETWORK_HANDLE) {
405             networkHandle = mNetworkHandle;
406         }
407         synchronized (mLock) {
408             checkHaveAdapter();
409             return new CronetBidirectionalStream(
410                     this,
411                     url,
412                     priority,
413                     callback,
414                     executor,
415                     httpMethod,
416                     requestHeaders,
417                     delayRequestHeadersUntilFirstFlush,
418                     requestAnnotations,
419                     trafficStatsTagSet,
420                     trafficStatsTag,
421                     trafficStatsUidSet,
422                     trafficStatsUid,
423                     networkHandle);
424         }
425     }
426 
427     @Override
getVersionString()428     public String getVersionString() {
429         return "Cronet/" + ImplVersion.getCronetVersionWithLastChange();
430     }
431 
432     @Override
getActiveRequestCount()433     public int getActiveRequestCount() {
434         return mActiveRequestCount.get();
435     }
436 
buildCronetVersion()437     private CronetVersion buildCronetVersion() {
438         return new CronetVersion(ApiVersion.getCronetVersion());
439     }
440 
441     @Override
shutdown()442     public void shutdown() {
443         if (mInUseStoragePath != null) {
444             synchronized (sInUseStoragePaths) {
445                 sInUseStoragePaths.remove(mInUseStoragePath);
446             }
447         }
448         synchronized (mLock) {
449             checkHaveAdapter();
450             if (mRunningRequestCount.get() != 0) {
451                 throw new IllegalStateException("Cannot shutdown with running requests.");
452             }
453             // Destroying adapter stops the network thread, so it cannot be
454             // called on network thread.
455             if (Thread.currentThread() == mNetworkThread) {
456                 throw new IllegalThreadStateException("Cannot shutdown from network thread.");
457             }
458         }
459         // Wait for init to complete on init and network thread (without lock,
460         // so other thread could access it).
461         mInitCompleted.block();
462 
463         // If not logging, this is a no-op.
464         stopNetLog();
465 
466         synchronized (mLock) {
467             // It is possible that adapter is already destroyed on another thread.
468             if (!haveRequestContextAdapter()) {
469                 return;
470             }
471             CronetUrlRequestContextJni.get()
472                     .destroy(mUrlRequestContextAdapter, CronetUrlRequestContext.this);
473             mUrlRequestContextAdapter = 0;
474         }
475     }
476 
477     @Override
startNetLogToFile(String fileName, boolean logAll)478     public void startNetLogToFile(String fileName, boolean logAll) {
479         synchronized (mLock) {
480             checkHaveAdapter();
481             if (mIsLogging) {
482                 return;
483             }
484             if (!CronetUrlRequestContextJni.get()
485                     .startNetLogToFile(
486                             mUrlRequestContextAdapter,
487                             CronetUrlRequestContext.this,
488                             fileName,
489                             logAll)) {
490                 throw new RuntimeException("Unable to start NetLog");
491             }
492             mIsLogging = true;
493         }
494     }
495 
496     @Override
startNetLogToDisk(String dirPath, boolean logAll, int maxSize)497     public void startNetLogToDisk(String dirPath, boolean logAll, int maxSize) {
498         synchronized (mLock) {
499             checkHaveAdapter();
500             if (mIsLogging) {
501                 return;
502             }
503             CronetUrlRequestContextJni.get()
504                     .startNetLogToDisk(
505                             mUrlRequestContextAdapter,
506                             CronetUrlRequestContext.this,
507                             dirPath,
508                             logAll,
509                             maxSize);
510             mIsLogging = true;
511         }
512     }
513 
514     @Override
stopNetLog()515     public void stopNetLog() {
516         synchronized (mLock) {
517             checkHaveAdapter();
518             if (!mIsLogging || mIsStoppingNetLog) {
519                 return;
520             }
521             CronetUrlRequestContextJni.get()
522                     .stopNetLog(mUrlRequestContextAdapter, CronetUrlRequestContext.this);
523             mIsStoppingNetLog = true;
524         }
525         mStopNetLogCompleted.block();
526         mStopNetLogCompleted.close();
527         synchronized (mLock) {
528             mIsStoppingNetLog = false;
529             mIsLogging = false;
530         }
531     }
532 
533     @CalledByNative
stopNetLogCompleted()534     public void stopNetLogCompleted() {
535         mStopNetLogCompleted.open();
536     }
537 
538     // This method is intentionally non-static to ensure Cronet native library
539     // is loaded by class constructor.
540     @Override
getGlobalMetricsDeltas()541     public byte[] getGlobalMetricsDeltas() {
542         return CronetUrlRequestContextJni.get().getHistogramDeltas();
543     }
544 
545     @Override
getEffectiveConnectionType()546     public int getEffectiveConnectionType() {
547         if (!mNetworkQualityEstimatorEnabled) {
548             throw new IllegalStateException("Network quality estimator must be enabled");
549         }
550         synchronized (mNetworkQualityLock) {
551             return convertConnectionTypeToApiValue(mEffectiveConnectionType);
552         }
553     }
554 
555     @Override
getHttpRttMs()556     public int getHttpRttMs() {
557         if (!mNetworkQualityEstimatorEnabled) {
558             throw new IllegalStateException("Network quality estimator must be enabled");
559         }
560         synchronized (mNetworkQualityLock) {
561             return mHttpRttMs != RttThroughputValues.INVALID_RTT_THROUGHPUT
562                     ? mHttpRttMs
563                     : CONNECTION_METRIC_UNKNOWN;
564         }
565     }
566 
567     @Override
getTransportRttMs()568     public int getTransportRttMs() {
569         if (!mNetworkQualityEstimatorEnabled) {
570             throw new IllegalStateException("Network quality estimator must be enabled");
571         }
572         synchronized (mNetworkQualityLock) {
573             return mTransportRttMs != RttThroughputValues.INVALID_RTT_THROUGHPUT
574                     ? mTransportRttMs
575                     : CONNECTION_METRIC_UNKNOWN;
576         }
577     }
578 
579     @Override
getDownstreamThroughputKbps()580     public int getDownstreamThroughputKbps() {
581         if (!mNetworkQualityEstimatorEnabled) {
582             throw new IllegalStateException("Network quality estimator must be enabled");
583         }
584         synchronized (mNetworkQualityLock) {
585             return mDownstreamThroughputKbps != RttThroughputValues.INVALID_RTT_THROUGHPUT
586                     ? mDownstreamThroughputKbps
587                     : CONNECTION_METRIC_UNKNOWN;
588         }
589     }
590 
591     @Override
bindToNetwork(long networkHandle)592     public void bindToNetwork(long networkHandle) {
593         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
594             throw new UnsupportedOperationException(
595                     "The multi-network API is available starting from Android Marshmallow");
596         }
597         mNetworkHandle = networkHandle;
598     }
599 
600     @VisibleForTesting
601     @Override
configureNetworkQualityEstimatorForTesting( boolean useLocalHostRequests, boolean useSmallerResponses, boolean disableOfflineCheck)602     public void configureNetworkQualityEstimatorForTesting(
603             boolean useLocalHostRequests,
604             boolean useSmallerResponses,
605             boolean disableOfflineCheck) {
606         if (!mNetworkQualityEstimatorEnabled) {
607             throw new IllegalStateException("Network quality estimator must be enabled");
608         }
609         synchronized (mLock) {
610             checkHaveAdapter();
611             CronetUrlRequestContextJni.get()
612                     .configureNetworkQualityEstimatorForTesting(
613                             mUrlRequestContextAdapter,
614                             CronetUrlRequestContext.this,
615                             useLocalHostRequests,
616                             useSmallerResponses,
617                             disableOfflineCheck);
618         }
619     }
620 
621     @Override
addRttListener(NetworkQualityRttListener listener)622     public void addRttListener(NetworkQualityRttListener listener) {
623         if (!mNetworkQualityEstimatorEnabled) {
624             throw new IllegalStateException("Network quality estimator must be enabled");
625         }
626         synchronized (mNetworkQualityLock) {
627             if (mRttListenerList.isEmpty()) {
628                 synchronized (mLock) {
629                     checkHaveAdapter();
630                     CronetUrlRequestContextJni.get()
631                             .provideRTTObservations(
632                                     mUrlRequestContextAdapter, CronetUrlRequestContext.this, true);
633                 }
634             }
635             mRttListenerList.addObserver(
636                     new VersionSafeCallbacks.NetworkQualityRttListenerWrapper(listener));
637         }
638     }
639 
640     @Override
removeRttListener(NetworkQualityRttListener listener)641     public void removeRttListener(NetworkQualityRttListener listener) {
642         if (!mNetworkQualityEstimatorEnabled) {
643             throw new IllegalStateException("Network quality estimator must be enabled");
644         }
645         synchronized (mNetworkQualityLock) {
646             if (mRttListenerList.removeObserver(
647                     new VersionSafeCallbacks.NetworkQualityRttListenerWrapper(listener))) {
648                 if (mRttListenerList.isEmpty()) {
649                     synchronized (mLock) {
650                         checkHaveAdapter();
651                         CronetUrlRequestContextJni.get()
652                                 .provideRTTObservations(
653                                         mUrlRequestContextAdapter,
654                                         CronetUrlRequestContext.this,
655                                         false);
656                     }
657                 }
658             }
659         }
660     }
661 
662     @Override
addThroughputListener(NetworkQualityThroughputListener listener)663     public void addThroughputListener(NetworkQualityThroughputListener listener) {
664         if (!mNetworkQualityEstimatorEnabled) {
665             throw new IllegalStateException("Network quality estimator must be enabled");
666         }
667         synchronized (mNetworkQualityLock) {
668             if (mThroughputListenerList.isEmpty()) {
669                 synchronized (mLock) {
670                     checkHaveAdapter();
671                     CronetUrlRequestContextJni.get()
672                             .provideThroughputObservations(
673                                     mUrlRequestContextAdapter, CronetUrlRequestContext.this, true);
674                 }
675             }
676             mThroughputListenerList.addObserver(
677                     new VersionSafeCallbacks.NetworkQualityThroughputListenerWrapper(listener));
678         }
679     }
680 
681     @Override
removeThroughputListener(NetworkQualityThroughputListener listener)682     public void removeThroughputListener(NetworkQualityThroughputListener listener) {
683         if (!mNetworkQualityEstimatorEnabled) {
684             throw new IllegalStateException("Network quality estimator must be enabled");
685         }
686         synchronized (mNetworkQualityLock) {
687             if (mThroughputListenerList.removeObserver(
688                     new VersionSafeCallbacks.NetworkQualityThroughputListenerWrapper(listener))) {
689                 if (mThroughputListenerList.isEmpty()) {
690                     synchronized (mLock) {
691                         checkHaveAdapter();
692                         CronetUrlRequestContextJni.get()
693                                 .provideThroughputObservations(
694                                         mUrlRequestContextAdapter,
695                                         CronetUrlRequestContext.this,
696                                         false);
697                     }
698                 }
699             }
700         }
701     }
702 
703     @Override
addRequestFinishedListener(RequestFinishedInfo.Listener listener)704     public void addRequestFinishedListener(RequestFinishedInfo.Listener listener) {
705         synchronized (mFinishedListenerLock) {
706             mFinishedListenerMap.put(
707                     listener, new VersionSafeCallbacks.RequestFinishedInfoListener(listener));
708         }
709     }
710 
711     @Override
removeRequestFinishedListener(RequestFinishedInfo.Listener listener)712     public void removeRequestFinishedListener(RequestFinishedInfo.Listener listener) {
713         synchronized (mFinishedListenerLock) {
714             mFinishedListenerMap.remove(listener);
715         }
716     }
717 
hasRequestFinishedListener()718     boolean hasRequestFinishedListener() {
719         synchronized (mFinishedListenerLock) {
720             return !mFinishedListenerMap.isEmpty();
721         }
722     }
723 
724     @Override
openConnection(URL url)725     public URLConnection openConnection(URL url) {
726         return openConnection(url, Proxy.NO_PROXY);
727     }
728 
729     @Override
openConnection(URL url, Proxy proxy)730     public URLConnection openConnection(URL url, Proxy proxy) {
731         if (proxy.type() != Proxy.Type.DIRECT) {
732             throw new UnsupportedOperationException();
733         }
734         String protocol = url.getProtocol();
735         if ("http".equals(protocol) || "https".equals(protocol)) {
736             return new CronetHttpURLConnection(url, this);
737         }
738         throw new UnsupportedOperationException("Unexpected protocol:" + protocol);
739     }
740 
741     @Override
createURLStreamHandlerFactory()742     public URLStreamHandlerFactory createURLStreamHandlerFactory() {
743         return new CronetURLStreamHandlerFactory(this);
744     }
745 
746     /**
747      * Mark request as started for the purposes of getActiveRequestCount(), and
748      * to prevent shutdown when there are running requests.
749      */
onRequestStarted()750     void onRequestStarted() {
751         mActiveRequestCount.incrementAndGet();
752         mRunningRequestCount.incrementAndGet();
753     }
754 
755     /**
756      * Mark request as destroyed to allow shutdown when there are no running
757      * requests. Should be called *before* the terminal callback is called, so
758      * that users can call shutdown() from the terminal callback.
759      */
onRequestDestroyed()760     void onRequestDestroyed() {
761         mRunningRequestCount.decrementAndGet();
762     }
763 
764     /**
765      * Mark request as finished for the purposes of getActiveRequestCount().
766      * Should be called *after* the terminal callback returns.
767      */
onRequestFinished()768     void onRequestFinished() {
769         mActiveRequestCount.decrementAndGet();
770     }
771 
772     @VisibleForTesting
getUrlRequestContextAdapter()773     public long getUrlRequestContextAdapter() {
774         synchronized (mLock) {
775             checkHaveAdapter();
776             return mUrlRequestContextAdapter;
777         }
778     }
779 
780     @GuardedBy("mLock")
checkHaveAdapter()781     private void checkHaveAdapter() throws IllegalStateException {
782         if (!haveRequestContextAdapter()) {
783             throw new IllegalStateException("Engine is shut down.");
784         }
785     }
786 
787     @GuardedBy("mLock")
haveRequestContextAdapter()788     private boolean haveRequestContextAdapter() {
789         return mUrlRequestContextAdapter != 0;
790     }
791 
convertConnectionTypeToApiValue(@ffectiveConnectionType int type)792     private static int convertConnectionTypeToApiValue(@EffectiveConnectionType int type) {
793         switch (type) {
794             case EffectiveConnectionType.TYPE_OFFLINE:
795                 return EFFECTIVE_CONNECTION_TYPE_OFFLINE;
796             case EffectiveConnectionType.TYPE_SLOW_2G:
797                 return EFFECTIVE_CONNECTION_TYPE_SLOW_2G;
798             case EffectiveConnectionType.TYPE_2G:
799                 return EFFECTIVE_CONNECTION_TYPE_2G;
800             case EffectiveConnectionType.TYPE_3G:
801                 return EFFECTIVE_CONNECTION_TYPE_3G;
802             case EffectiveConnectionType.TYPE_4G:
803                 return EFFECTIVE_CONNECTION_TYPE_4G;
804             case EffectiveConnectionType.TYPE_UNKNOWN:
805                 return EFFECTIVE_CONNECTION_TYPE_UNKNOWN;
806             default:
807                 throw new RuntimeException(
808                         "Internal Error: Illegal EffectiveConnectionType value " + type);
809         }
810     }
811 
812     @SuppressWarnings("unused")
813     @CalledByNative
initNetworkThread()814     private void initNetworkThread() {
815         mNetworkThread = Thread.currentThread();
816         mInitCompleted.open();
817         Thread.currentThread().setName("ChromiumNet");
818     }
819 
820     @SuppressWarnings("unused")
821     @CalledByNative
onEffectiveConnectionTypeChanged(int effectiveConnectionType)822     private void onEffectiveConnectionTypeChanged(int effectiveConnectionType) {
823         synchronized (mNetworkQualityLock) {
824             // Convert the enum returned by the network quality estimator to an enum of type
825             // EffectiveConnectionType.
826             mEffectiveConnectionType = effectiveConnectionType;
827         }
828     }
829 
830     @SuppressWarnings("unused")
831     @CalledByNative
onRTTOrThroughputEstimatesComputed( final int httpRttMs, final int transportRttMs, final int downstreamThroughputKbps)832     private void onRTTOrThroughputEstimatesComputed(
833             final int httpRttMs, final int transportRttMs, final int downstreamThroughputKbps) {
834         synchronized (mNetworkQualityLock) {
835             mHttpRttMs = httpRttMs;
836             mTransportRttMs = transportRttMs;
837             mDownstreamThroughputKbps = downstreamThroughputKbps;
838         }
839     }
840 
841     @SuppressWarnings("unused")
842     @CalledByNative
onRttObservation(final int rttMs, final long whenMs, final int source)843     private void onRttObservation(final int rttMs, final long whenMs, final int source) {
844         synchronized (mNetworkQualityLock) {
845             for (final VersionSafeCallbacks.NetworkQualityRttListenerWrapper listener :
846                     mRttListenerList) {
847                 Runnable task =
848                         new Runnable() {
849                             @Override
850                             public void run() {
851                                 listener.onRttObservation(rttMs, whenMs, source);
852                             }
853                         };
854                 postObservationTaskToExecutor(listener.getExecutor(), task);
855             }
856         }
857     }
858 
859     @SuppressWarnings("unused")
860     @CalledByNative
onThroughputObservation( final int throughputKbps, final long whenMs, final int source)861     private void onThroughputObservation(
862             final int throughputKbps, final long whenMs, final int source) {
863         synchronized (mNetworkQualityLock) {
864             for (final VersionSafeCallbacks.NetworkQualityThroughputListenerWrapper listener :
865                     mThroughputListenerList) {
866                 Runnable task =
867                         new Runnable() {
868                             @Override
869                             public void run() {
870                                 listener.onThroughputObservation(throughputKbps, whenMs, source);
871                             }
872                         };
873                 postObservationTaskToExecutor(listener.getExecutor(), task);
874             }
875         }
876     }
877 
reportRequestFinished( final RequestFinishedInfo requestInfo, RefCountDelegate inflightCallbackCount)878     void reportRequestFinished(
879             final RequestFinishedInfo requestInfo, RefCountDelegate inflightCallbackCount) {
880         ArrayList<VersionSafeCallbacks.RequestFinishedInfoListener> currentListeners;
881         synchronized (mFinishedListenerLock) {
882             if (mFinishedListenerMap.isEmpty()) return;
883             currentListeners =
884                     new ArrayList<VersionSafeCallbacks.RequestFinishedInfoListener>(
885                             mFinishedListenerMap.values());
886         }
887         for (final VersionSafeCallbacks.RequestFinishedInfoListener listener : currentListeners) {
888             Runnable task =
889                     new Runnable() {
890                         @Override
891                         public void run() {
892                             listener.onRequestFinished(requestInfo);
893                         }
894                     };
895             postObservationTaskToExecutor(listener.getExecutor(), task, inflightCallbackCount);
896         }
897     }
898 
postObservationTaskToExecutor( Executor executor, Runnable task, RefCountDelegate inflightCallbackCount)899     private static void postObservationTaskToExecutor(
900             Executor executor, Runnable task, RefCountDelegate inflightCallbackCount) {
901         if (inflightCallbackCount != null) inflightCallbackCount.increment();
902         try {
903             executor.execute(
904                     () -> {
905                         try {
906                             task.run();
907                         } catch (Exception e) {
908                             Log.e(LOG_TAG, "Exception thrown from observation task", e);
909                         } finally {
910                             if (inflightCallbackCount != null) inflightCallbackCount.decrement();
911                         }
912                     });
913         } catch (RejectedExecutionException failException) {
914             if (inflightCallbackCount != null) inflightCallbackCount.decrement();
915             Log.e(
916                     CronetUrlRequestContext.LOG_TAG,
917                     "Exception posting task to executor",
918                     failException);
919         }
920     }
921 
postObservationTaskToExecutor(Executor executor, Runnable task)922     private static void postObservationTaskToExecutor(Executor executor, Runnable task) {
923         postObservationTaskToExecutor(executor, task, null);
924     }
925 
isNetworkThread(Thread thread)926     public boolean isNetworkThread(Thread thread) {
927         return thread == mNetworkThread;
928     }
929 
930     // Native methods are implemented in cronet_url_request_context_adapter.cc.
931     @NativeMethods
932     interface Natives {
createRequestContextConfig(byte[] serializedRequestContextConfigOptions)933         long createRequestContextConfig(byte[] serializedRequestContextConfigOptions);
934 
addQuicHint(long urlRequestContextConfig, String host, int port, int alternatePort)935         void addQuicHint(long urlRequestContextConfig, String host, int port, int alternatePort);
936 
addPkp( long urlRequestContextConfig, String host, byte[][] hashes, boolean includeSubdomains, long expirationTime)937         void addPkp(
938                 long urlRequestContextConfig,
939                 String host,
940                 byte[][] hashes,
941                 boolean includeSubdomains,
942                 long expirationTime);
943 
createRequestContextAdapter(long urlRequestContextConfig)944         long createRequestContextAdapter(long urlRequestContextConfig);
945 
getHistogramDeltas()946         byte[] getHistogramDeltas();
947 
948         @NativeClassQualifiedName("CronetContextAdapter")
destroy(long nativePtr, CronetUrlRequestContext caller)949         void destroy(long nativePtr, CronetUrlRequestContext caller);
950 
951         @NativeClassQualifiedName("CronetContextAdapter")
startNetLogToFile( long nativePtr, CronetUrlRequestContext caller, String fileName, boolean logAll)952         boolean startNetLogToFile(
953                 long nativePtr, CronetUrlRequestContext caller, String fileName, boolean logAll);
954 
955         @NativeClassQualifiedName("CronetContextAdapter")
startNetLogToDisk( long nativePtr, CronetUrlRequestContext caller, String dirPath, boolean logAll, int maxSize)956         void startNetLogToDisk(
957                 long nativePtr,
958                 CronetUrlRequestContext caller,
959                 String dirPath,
960                 boolean logAll,
961                 int maxSize);
962 
963         @NativeClassQualifiedName("CronetContextAdapter")
stopNetLog(long nativePtr, CronetUrlRequestContext caller)964         void stopNetLog(long nativePtr, CronetUrlRequestContext caller);
965 
966         @NativeClassQualifiedName("CronetContextAdapter")
initRequestContextOnInitThread(long nativePtr, CronetUrlRequestContext caller)967         void initRequestContextOnInitThread(long nativePtr, CronetUrlRequestContext caller);
968 
969         @NativeClassQualifiedName("CronetContextAdapter")
configureNetworkQualityEstimatorForTesting( long nativePtr, CronetUrlRequestContext caller, boolean useLocalHostRequests, boolean useSmallerResponses, boolean disableOfflineCheck)970         void configureNetworkQualityEstimatorForTesting(
971                 long nativePtr,
972                 CronetUrlRequestContext caller,
973                 boolean useLocalHostRequests,
974                 boolean useSmallerResponses,
975                 boolean disableOfflineCheck);
976 
977         @NativeClassQualifiedName("CronetContextAdapter")
provideRTTObservations(long nativePtr, CronetUrlRequestContext caller, boolean should)978         void provideRTTObservations(long nativePtr, CronetUrlRequestContext caller, boolean should);
979 
980         @NativeClassQualifiedName("CronetContextAdapter")
provideThroughputObservations( long nativePtr, CronetUrlRequestContext caller, boolean should)981         void provideThroughputObservations(
982                 long nativePtr, CronetUrlRequestContext caller, boolean should);
983 
984         @NativeClassQualifiedName("CronetContextAdapter")
getEnableTelemetry(long nativePtr, CronetUrlRequestContext caller)985         boolean getEnableTelemetry(long nativePtr, CronetUrlRequestContext caller);
986     }
987 }
988