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 }