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