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