blob: d3dd5924779a1b5560d359d496461baef93304b4 [file] [log] [blame]
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net.impl;
import org.chromium.net.ApiVersion;
import android.os.Build;
import android.os.ConditionVariable;
import android.os.Process;
import androidx.annotation.VisibleForTesting;
import org.jni_zero.CalledByNative;
import org.jni_zero.JNINamespace;
import org.jni_zero.NativeClassQualifiedName;
import org.jni_zero.NativeMethods;
import org.chromium.base.Log;
import org.chromium.base.ObserverList;
import org.chromium.build.annotations.UsedByReflection;
import org.chromium.net.BidirectionalStream;
import org.chromium.net.CronetEngine;
import org.chromium.net.EffectiveConnectionType;
import org.chromium.net.ExperimentalBidirectionalStream;
import org.chromium.net.NetworkQualityRttListener;
import org.chromium.net.NetworkQualityThroughputListener;
import org.chromium.net.RequestContextConfigOptions;
import org.chromium.net.RequestFinishedInfo;
import org.chromium.net.RttThroughputValues;
import org.chromium.net.UrlRequest;
import org.chromium.net.impl.CronetLogger.CronetEngineBuilderInfo;
import org.chromium.net.impl.CronetLogger.CronetSource;
import org.chromium.net.impl.CronetLogger.CronetVersion;
import org.chromium.net.urlconnection.CronetHttpURLConnection;
import org.chromium.net.urlconnection.CronetURLStreamHandlerFactory;
import java.net.Proxy;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandlerFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.concurrent.GuardedBy;
/** CronetEngine using Chromium HTTP stack implementation. */
@JNINamespace("cronet")
@UsedByReflection("CronetEngine.java")
@VisibleForTesting
public class CronetUrlRequestContext extends CronetEngineBase {
static final String LOG_TAG = CronetUrlRequestContext.class.getSimpleName();
/** Synchronize access to mUrlRequestContextAdapter and shutdown routine. */
private final Object mLock = new Object();
private final ConditionVariable mInitCompleted = new ConditionVariable(false);
/**
* The number of started requests where the terminal callback (i.e.
* onSucceeded/onCancelled/onFailed) has not yet been called.
*/
private final AtomicInteger mRunningRequestCount = new AtomicInteger(0);
/*
* The number of started requests where the terminal callbacks (i.e.
* onSucceeded/onCancelled/onFailed, request finished listeners) have not
* all returned yet.
*
* By definition this is always greater than or equal to
* mRunningRequestCount. The difference between the two is the number of
* terminal callbacks that are currently running.
*/
private final AtomicInteger mActiveRequestCount = new AtomicInteger(0);
@GuardedBy("mLock")
private long mUrlRequestContextAdapter;
/**
* This field is accessed without synchronization, but only for the purposes of reference
* equality comparison with other threads. If such a comparison is performed on the network
* thread, then there is a happens-before edge between the write of this field and the
* subsequent read; if it's performed on another thread, then observing a value of null won't
* change the result of the comparison.
*/
private Thread mNetworkThread;
private final boolean mNetworkQualityEstimatorEnabled;
/**
* Locks operations on network quality listeners, because listener
* addition and removal may occur on a different thread from notification.
*/
private final Object mNetworkQualityLock = new Object();
/**
* Locks operations on the list of RequestFinishedInfo.Listeners, because operations can happen
* on any thread. This should be used for fine-grained locking only. In particular, don't call
* any UrlRequest methods that acquire mUrlRequestAdapterLock while holding this lock.
*/
private final Object mFinishedListenerLock = new Object();
/**
* Current effective connection type as computed by the network quality
* estimator.
*/
@GuardedBy("mNetworkQualityLock")
private int mEffectiveConnectionType = EffectiveConnectionType.TYPE_UNKNOWN;
/**
* Current estimate of the HTTP RTT (in milliseconds) computed by the
* network quality estimator.
*/
@GuardedBy("mNetworkQualityLock")
private int mHttpRttMs = RttThroughputValues.INVALID_RTT_THROUGHPUT;
/**
* Current estimate of the transport RTT (in milliseconds) computed by the
* network quality estimator.
*/
@GuardedBy("mNetworkQualityLock")
private int mTransportRttMs = RttThroughputValues.INVALID_RTT_THROUGHPUT;
/**
* Current estimate of the downstream throughput (in kilobits per second)
* computed by the network quality estimator.
*/
@GuardedBy("mNetworkQualityLock")
private int mDownstreamThroughputKbps = RttThroughputValues.INVALID_RTT_THROUGHPUT;
@GuardedBy("mNetworkQualityLock")
private final ObserverList<VersionSafeCallbacks.NetworkQualityRttListenerWrapper>
mRttListenerList =
new ObserverList<VersionSafeCallbacks.NetworkQualityRttListenerWrapper>();
@GuardedBy("mNetworkQualityLock")
private final ObserverList<VersionSafeCallbacks.NetworkQualityThroughputListenerWrapper>
mThroughputListenerList =
new ObserverList<
VersionSafeCallbacks.NetworkQualityThroughputListenerWrapper>();
@GuardedBy("mFinishedListenerLock")
private final Map<
RequestFinishedInfo.Listener, VersionSafeCallbacks.RequestFinishedInfoListener>
mFinishedListenerMap =
new HashMap<
RequestFinishedInfo.Listener,
VersionSafeCallbacks.RequestFinishedInfoListener>();
private final ConditionVariable mStopNetLogCompleted = new ConditionVariable();
/** Set of storage paths currently in use. */
@GuardedBy("sInUseStoragePaths")
private static final HashSet<String> sInUseStoragePaths = new HashSet<String>();
/** Storage path used by this context. */
private final String mInUseStoragePath;
/** True if a NetLog observer is active. */
@GuardedBy("mLock")
private boolean mIsLogging;
/** True if NetLog is being shutdown. */
@GuardedBy("mLock")
private boolean mIsStoppingNetLog;
/** The network handle to be used for requests that do not explicitly specify one. **/
private long mNetworkHandle = DEFAULT_NETWORK_HANDLE;
private final int mCronetEngineId;
/** Whether Cronet Telemetry should be enabled or not. */
private final boolean mEnableTelemetry;
/** The logger to be used for logging. */
private final CronetLogger mLogger;
int getCronetEngineId() {
return mCronetEngineId;
}
CronetLogger getCronetLogger() {
return mLogger;
}
public boolean getEnableTelemetryForTesting() {
return mEnableTelemetry;
}
@UsedByReflection("CronetEngine.java")
public CronetUrlRequestContext(final CronetEngineBuilderImpl builder) {
mCronetEngineId = hashCode();
mRttListenerList.disableThreadAsserts();
mThroughputListenerList.disableThreadAsserts();
mNetworkQualityEstimatorEnabled = builder.networkQualityEstimatorEnabled();
CronetLibraryLoader.ensureInitialized(builder.getContext(), builder);
if (builder.httpCacheMode() == HttpCacheType.DISK) {
mInUseStoragePath = builder.storagePath();
synchronized (sInUseStoragePaths) {
if (!sInUseStoragePaths.add(mInUseStoragePath)) {
throw new IllegalStateException("Disk cache storage path already in use");
}
}
} else {
mInUseStoragePath = null;
}
synchronized (mLock) {
mUrlRequestContextAdapter =
CronetUrlRequestContextJni.get()
.createRequestContextAdapter(
createNativeUrlRequestContextConfig(builder));
if (mUrlRequestContextAdapter == 0) {
throw new NullPointerException("Context Adapter creation failed.");
}
mEnableTelemetry =
CronetUrlRequestContextJni.get()
.getEnableTelemetry(
mUrlRequestContextAdapter, CronetUrlRequestContext.this);
}
if (mEnableTelemetry) {
mLogger = CronetLoggerFactory.createLogger(builder.getContext(), getCronetSource());
} else {
mLogger = CronetLoggerFactory.createNoOpLogger();
}
try {
mLogger.logCronetEngineCreation(
getCronetEngineId(),
new CronetEngineBuilderInfo(builder),
buildCronetVersion(),
getCronetSource());
} catch (RuntimeException e) {
// Handle any issue gracefully, we should never crash due failures while logging.
Log.e(LOG_TAG, "Error while trying to log CronetEngine creation: ", e);
}
// Init native Chromium URLRequestContext on init thread.
CronetLibraryLoader.postToInitThread(
new Runnable() {
@Override
public void run() {
CronetLibraryLoader.ensureInitializedOnInitThread();
synchronized (mLock) {
// mUrlRequestContextAdapter is guaranteed to exist until
// initialization on init and network threads completes and
// initNetworkThread is called back on network thread.
CronetUrlRequestContextJni.get()
.initRequestContextOnInitThread(
mUrlRequestContextAdapter,
CronetUrlRequestContext.this);
}
}
});
}
static CronetSource getCronetSource() {
ClassLoader implClassLoader = CronetUrlRequest.class.getClassLoader();
if (implClassLoader.toString().startsWith("java.lang.BootClassLoader")) {
return CronetSource.CRONET_SOURCE_PLATFORM;
}
ClassLoader apiClassLoader = CronetEngine.class.getClassLoader();
return apiClassLoader.equals(implClassLoader)
? CronetSource.CRONET_SOURCE_STATICALLY_LINKED
: CronetSource.CRONET_SOURCE_PLAY_SERVICES;
}
@VisibleForTesting
public static long createNativeUrlRequestContextConfig(CronetEngineBuilderImpl builder) {
final long urlRequestContextConfig =
CronetUrlRequestContextJni.get()
.createRequestContextConfig(
createRequestContextConfigOptions(builder).toByteArray());
if (urlRequestContextConfig == 0) {
throw new IllegalArgumentException("Experimental options parsing failed.");
}
for (CronetEngineBuilderImpl.QuicHint quicHint : builder.quicHints()) {
CronetUrlRequestContextJni.get()
.addQuicHint(
urlRequestContextConfig,
quicHint.mHost,
quicHint.mPort,
quicHint.mAlternatePort);
}
for (CronetEngineBuilderImpl.Pkp pkp : builder.publicKeyPins()) {
CronetUrlRequestContextJni.get()
.addPkp(
urlRequestContextConfig,
pkp.mHost,
pkp.mHashes,
pkp.mIncludeSubdomains,
pkp.mExpirationDate.getTime());
}
return urlRequestContextConfig;
}
private static RequestContextConfigOptions createRequestContextConfigOptions(
CronetEngineBuilderImpl engineBuilder) {
RequestContextConfigOptions.Builder resultBuilder =
RequestContextConfigOptions.newBuilder()
.setQuicEnabled(engineBuilder.quicEnabled())
.setHttp2Enabled(engineBuilder.http2Enabled())
.setBrotliEnabled(engineBuilder.brotliEnabled())
.setDisableCache(engineBuilder.cacheDisabled())
.setHttpCacheMode(engineBuilder.httpCacheMode())
.setHttpCacheMaxSize(engineBuilder.httpCacheMaxSize())
.setMockCertVerifier(engineBuilder.mockCertVerifier())
.setEnableNetworkQualityEstimator(
engineBuilder.networkQualityEstimatorEnabled())
.setBypassPublicKeyPinningForLocalTrustAnchors(
engineBuilder.publicKeyPinningBypassForLocalTrustAnchorsEnabled())
.setNetworkThreadPriority(
engineBuilder.threadPriority(Process.THREAD_PRIORITY_BACKGROUND));
if (engineBuilder.getUserAgent() != null) {
resultBuilder.setUserAgent(engineBuilder.getUserAgent());
}
if (engineBuilder.storagePath() != null) {
resultBuilder.setStoragePath(engineBuilder.storagePath());
}
if (engineBuilder.getDefaultQuicUserAgentId() != null) {
resultBuilder.setQuicDefaultUserAgentId(engineBuilder.getDefaultQuicUserAgentId());
}
if (engineBuilder.experimentalOptions() != null) {
resultBuilder.setExperimentalOptions(engineBuilder.experimentalOptions());
}
return resultBuilder.build();
}
@Override
public ExperimentalBidirectionalStream.Builder newBidirectionalStreamBuilder(
String url, BidirectionalStream.Callback callback, Executor executor) {
return new BidirectionalStreamBuilderImpl(url, callback, executor, this);
}
@Override
public UrlRequestBase 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) {
// if this request is not bound to network, use the network bound to the engine.
if (networkHandle == DEFAULT_NETWORK_HANDLE) {
networkHandle = mNetworkHandle;
}
synchronized (mLock) {
checkHaveAdapter();
return new CronetUrlRequest(
this,
url,
priority,
callback,
executor,
requestAnnotations,
disableCache,
disableConnectionMigration,
allowDirectExecutor,
trafficStatsTagSet,
trafficStatsTag,
trafficStatsUidSet,
trafficStatsUid,
requestFinishedListener,
idempotency,
networkHandle);
}
}
@Override
protected ExperimentalBidirectionalStream 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) {
if (networkHandle == DEFAULT_NETWORK_HANDLE) {
networkHandle = mNetworkHandle;
}
synchronized (mLock) {
checkHaveAdapter();
return new CronetBidirectionalStream(
this,
url,
priority,
callback,
executor,
httpMethod,
requestHeaders,
delayRequestHeadersUntilFirstFlush,
requestAnnotations,
trafficStatsTagSet,
trafficStatsTag,
trafficStatsUidSet,
trafficStatsUid,
networkHandle);
}
}
@Override
public String getVersionString() {
return "Cronet/" + ImplVersion.getCronetVersionWithLastChange();
}
@Override
public int getActiveRequestCount() {
return mActiveRequestCount.get();
}
private CronetVersion buildCronetVersion() {
return new CronetVersion(ApiVersion.getCronetVersion());
}
@Override
public void shutdown() {
if (mInUseStoragePath != null) {
synchronized (sInUseStoragePaths) {
sInUseStoragePaths.remove(mInUseStoragePath);
}
}
synchronized (mLock) {
checkHaveAdapter();
if (mRunningRequestCount.get() != 0) {
throw new IllegalStateException("Cannot shutdown with running requests.");
}
// Destroying adapter stops the network thread, so it cannot be
// called on network thread.
if (Thread.currentThread() == mNetworkThread) {
throw new IllegalThreadStateException("Cannot shutdown from network thread.");
}
}
// Wait for init to complete on init and network thread (without lock,
// so other thread could access it).
mInitCompleted.block();
// If not logging, this is a no-op.
stopNetLog();
synchronized (mLock) {
// It is possible that adapter is already destroyed on another thread.
if (!haveRequestContextAdapter()) {
return;
}
CronetUrlRequestContextJni.get()
.destroy(mUrlRequestContextAdapter, CronetUrlRequestContext.this);
mUrlRequestContextAdapter = 0;
}
}
@Override
public void startNetLogToFile(String fileName, boolean logAll) {
synchronized (mLock) {
checkHaveAdapter();
if (mIsLogging) {
return;
}
if (!CronetUrlRequestContextJni.get()
.startNetLogToFile(
mUrlRequestContextAdapter,
CronetUrlRequestContext.this,
fileName,
logAll)) {
throw new RuntimeException("Unable to start NetLog");
}
mIsLogging = true;
}
}
@Override
public void startNetLogToDisk(String dirPath, boolean logAll, int maxSize) {
synchronized (mLock) {
checkHaveAdapter();
if (mIsLogging) {
return;
}
CronetUrlRequestContextJni.get()
.startNetLogToDisk(
mUrlRequestContextAdapter,
CronetUrlRequestContext.this,
dirPath,
logAll,
maxSize);
mIsLogging = true;
}
}
@Override
public void stopNetLog() {
synchronized (mLock) {
checkHaveAdapter();
if (!mIsLogging || mIsStoppingNetLog) {
return;
}
CronetUrlRequestContextJni.get()
.stopNetLog(mUrlRequestContextAdapter, CronetUrlRequestContext.this);
mIsStoppingNetLog = true;
}
mStopNetLogCompleted.block();
mStopNetLogCompleted.close();
synchronized (mLock) {
mIsStoppingNetLog = false;
mIsLogging = false;
}
}
@CalledByNative
public void stopNetLogCompleted() {
mStopNetLogCompleted.open();
}
// This method is intentionally non-static to ensure Cronet native library
// is loaded by class constructor.
@Override
public byte[] getGlobalMetricsDeltas() {
return CronetUrlRequestContextJni.get().getHistogramDeltas();
}
@Override
public int getEffectiveConnectionType() {
if (!mNetworkQualityEstimatorEnabled) {
throw new IllegalStateException("Network quality estimator must be enabled");
}
synchronized (mNetworkQualityLock) {
return convertConnectionTypeToApiValue(mEffectiveConnectionType);
}
}
@Override
public int getHttpRttMs() {
if (!mNetworkQualityEstimatorEnabled) {
throw new IllegalStateException("Network quality estimator must be enabled");
}
synchronized (mNetworkQualityLock) {
return mHttpRttMs != RttThroughputValues.INVALID_RTT_THROUGHPUT
? mHttpRttMs
: CONNECTION_METRIC_UNKNOWN;
}
}
@Override
public int getTransportRttMs() {
if (!mNetworkQualityEstimatorEnabled) {
throw new IllegalStateException("Network quality estimator must be enabled");
}
synchronized (mNetworkQualityLock) {
return mTransportRttMs != RttThroughputValues.INVALID_RTT_THROUGHPUT
? mTransportRttMs
: CONNECTION_METRIC_UNKNOWN;
}
}
@Override
public int getDownstreamThroughputKbps() {
if (!mNetworkQualityEstimatorEnabled) {
throw new IllegalStateException("Network quality estimator must be enabled");
}
synchronized (mNetworkQualityLock) {
return mDownstreamThroughputKbps != RttThroughputValues.INVALID_RTT_THROUGHPUT
? mDownstreamThroughputKbps
: CONNECTION_METRIC_UNKNOWN;
}
}
@Override
public void bindToNetwork(long networkHandle) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
throw new UnsupportedOperationException(
"The multi-network API is available starting from Android Marshmallow");
}
mNetworkHandle = networkHandle;
}
@VisibleForTesting
@Override
public void configureNetworkQualityEstimatorForTesting(
boolean useLocalHostRequests,
boolean useSmallerResponses,
boolean disableOfflineCheck) {
if (!mNetworkQualityEstimatorEnabled) {
throw new IllegalStateException("Network quality estimator must be enabled");
}
synchronized (mLock) {
checkHaveAdapter();
CronetUrlRequestContextJni.get()
.configureNetworkQualityEstimatorForTesting(
mUrlRequestContextAdapter,
CronetUrlRequestContext.this,
useLocalHostRequests,
useSmallerResponses,
disableOfflineCheck);
}
}
@Override
public void addRttListener(NetworkQualityRttListener listener) {
if (!mNetworkQualityEstimatorEnabled) {
throw new IllegalStateException("Network quality estimator must be enabled");
}
synchronized (mNetworkQualityLock) {
if (mRttListenerList.isEmpty()) {
synchronized (mLock) {
checkHaveAdapter();
CronetUrlRequestContextJni.get()
.provideRTTObservations(
mUrlRequestContextAdapter, CronetUrlRequestContext.this, true);
}
}
mRttListenerList.addObserver(
new VersionSafeCallbacks.NetworkQualityRttListenerWrapper(listener));
}
}
@Override
public void removeRttListener(NetworkQualityRttListener listener) {
if (!mNetworkQualityEstimatorEnabled) {
throw new IllegalStateException("Network quality estimator must be enabled");
}
synchronized (mNetworkQualityLock) {
if (mRttListenerList.removeObserver(
new VersionSafeCallbacks.NetworkQualityRttListenerWrapper(listener))) {
if (mRttListenerList.isEmpty()) {
synchronized (mLock) {
checkHaveAdapter();
CronetUrlRequestContextJni.get()
.provideRTTObservations(
mUrlRequestContextAdapter,
CronetUrlRequestContext.this,
false);
}
}
}
}
}
@Override
public void addThroughputListener(NetworkQualityThroughputListener listener) {
if (!mNetworkQualityEstimatorEnabled) {
throw new IllegalStateException("Network quality estimator must be enabled");
}
synchronized (mNetworkQualityLock) {
if (mThroughputListenerList.isEmpty()) {
synchronized (mLock) {
checkHaveAdapter();
CronetUrlRequestContextJni.get()
.provideThroughputObservations(
mUrlRequestContextAdapter, CronetUrlRequestContext.this, true);
}
}
mThroughputListenerList.addObserver(
new VersionSafeCallbacks.NetworkQualityThroughputListenerWrapper(listener));
}
}
@Override
public void removeThroughputListener(NetworkQualityThroughputListener listener) {
if (!mNetworkQualityEstimatorEnabled) {
throw new IllegalStateException("Network quality estimator must be enabled");
}
synchronized (mNetworkQualityLock) {
if (mThroughputListenerList.removeObserver(
new VersionSafeCallbacks.NetworkQualityThroughputListenerWrapper(listener))) {
if (mThroughputListenerList.isEmpty()) {
synchronized (mLock) {
checkHaveAdapter();
CronetUrlRequestContextJni.get()
.provideThroughputObservations(
mUrlRequestContextAdapter,
CronetUrlRequestContext.this,
false);
}
}
}
}
}
@Override
public void addRequestFinishedListener(RequestFinishedInfo.Listener listener) {
synchronized (mFinishedListenerLock) {
mFinishedListenerMap.put(
listener, new VersionSafeCallbacks.RequestFinishedInfoListener(listener));
}
}
@Override
public void removeRequestFinishedListener(RequestFinishedInfo.Listener listener) {
synchronized (mFinishedListenerLock) {
mFinishedListenerMap.remove(listener);
}
}
boolean hasRequestFinishedListener() {
synchronized (mFinishedListenerLock) {
return !mFinishedListenerMap.isEmpty();
}
}
@Override
public URLConnection openConnection(URL url) {
return openConnection(url, Proxy.NO_PROXY);
}
@Override
public URLConnection openConnection(URL url, Proxy proxy) {
if (proxy.type() != Proxy.Type.DIRECT) {
throw new UnsupportedOperationException();
}
String protocol = url.getProtocol();
if ("http".equals(protocol) || "https".equals(protocol)) {
return new CronetHttpURLConnection(url, this);
}
throw new UnsupportedOperationException("Unexpected protocol:" + protocol);
}
@Override
public URLStreamHandlerFactory createURLStreamHandlerFactory() {
return new CronetURLStreamHandlerFactory(this);
}
/**
* Mark request as started for the purposes of getActiveRequestCount(), and
* to prevent shutdown when there are running requests.
*/
void onRequestStarted() {
mActiveRequestCount.incrementAndGet();
mRunningRequestCount.incrementAndGet();
}
/**
* Mark request as destroyed to allow shutdown when there are no running
* requests. Should be called *before* the terminal callback is called, so
* that users can call shutdown() from the terminal callback.
*/
void onRequestDestroyed() {
mRunningRequestCount.decrementAndGet();
}
/**
* Mark request as finished for the purposes of getActiveRequestCount().
* Should be called *after* the terminal callback returns.
*/
void onRequestFinished() {
mActiveRequestCount.decrementAndGet();
}
@VisibleForTesting
public long getUrlRequestContextAdapter() {
synchronized (mLock) {
checkHaveAdapter();
return mUrlRequestContextAdapter;
}
}
@GuardedBy("mLock")
private void checkHaveAdapter() throws IllegalStateException {
if (!haveRequestContextAdapter()) {
throw new IllegalStateException("Engine is shut down.");
}
}
@GuardedBy("mLock")
private boolean haveRequestContextAdapter() {
return mUrlRequestContextAdapter != 0;
}
private static int convertConnectionTypeToApiValue(@EffectiveConnectionType int type) {
switch (type) {
case EffectiveConnectionType.TYPE_OFFLINE:
return EFFECTIVE_CONNECTION_TYPE_OFFLINE;
case EffectiveConnectionType.TYPE_SLOW_2G:
return EFFECTIVE_CONNECTION_TYPE_SLOW_2G;
case EffectiveConnectionType.TYPE_2G:
return EFFECTIVE_CONNECTION_TYPE_2G;
case EffectiveConnectionType.TYPE_3G:
return EFFECTIVE_CONNECTION_TYPE_3G;
case EffectiveConnectionType.TYPE_4G:
return EFFECTIVE_CONNECTION_TYPE_4G;
case EffectiveConnectionType.TYPE_UNKNOWN:
return EFFECTIVE_CONNECTION_TYPE_UNKNOWN;
default:
throw new RuntimeException(
"Internal Error: Illegal EffectiveConnectionType value " + type);
}
}
@SuppressWarnings("unused")
@CalledByNative
private void initNetworkThread() {
mNetworkThread = Thread.currentThread();
mInitCompleted.open();
Thread.currentThread().setName("ChromiumNet");
}
@SuppressWarnings("unused")
@CalledByNative
private void onEffectiveConnectionTypeChanged(int effectiveConnectionType) {
synchronized (mNetworkQualityLock) {
// Convert the enum returned by the network quality estimator to an enum of type
// EffectiveConnectionType.
mEffectiveConnectionType = effectiveConnectionType;
}
}
@SuppressWarnings("unused")
@CalledByNative
private void onRTTOrThroughputEstimatesComputed(
final int httpRttMs, final int transportRttMs, final int downstreamThroughputKbps) {
synchronized (mNetworkQualityLock) {
mHttpRttMs = httpRttMs;
mTransportRttMs = transportRttMs;
mDownstreamThroughputKbps = downstreamThroughputKbps;
}
}
@SuppressWarnings("unused")
@CalledByNative
private void onRttObservation(final int rttMs, final long whenMs, final int source) {
synchronized (mNetworkQualityLock) {
for (final VersionSafeCallbacks.NetworkQualityRttListenerWrapper listener :
mRttListenerList) {
Runnable task =
new Runnable() {
@Override
public void run() {
listener.onRttObservation(rttMs, whenMs, source);
}
};
postObservationTaskToExecutor(listener.getExecutor(), task);
}
}
}
@SuppressWarnings("unused")
@CalledByNative
private void onThroughputObservation(
final int throughputKbps, final long whenMs, final int source) {
synchronized (mNetworkQualityLock) {
for (final VersionSafeCallbacks.NetworkQualityThroughputListenerWrapper listener :
mThroughputListenerList) {
Runnable task =
new Runnable() {
@Override
public void run() {
listener.onThroughputObservation(throughputKbps, whenMs, source);
}
};
postObservationTaskToExecutor(listener.getExecutor(), task);
}
}
}
void reportRequestFinished(
final RequestFinishedInfo requestInfo, RefCountDelegate inflightCallbackCount) {
ArrayList<VersionSafeCallbacks.RequestFinishedInfoListener> currentListeners;
synchronized (mFinishedListenerLock) {
if (mFinishedListenerMap.isEmpty()) return;
currentListeners =
new ArrayList<VersionSafeCallbacks.RequestFinishedInfoListener>(
mFinishedListenerMap.values());
}
for (final VersionSafeCallbacks.RequestFinishedInfoListener listener : currentListeners) {
Runnable task =
new Runnable() {
@Override
public void run() {
listener.onRequestFinished(requestInfo);
}
};
postObservationTaskToExecutor(listener.getExecutor(), task, inflightCallbackCount);
}
}
private static void postObservationTaskToExecutor(
Executor executor, Runnable task, RefCountDelegate inflightCallbackCount) {
if (inflightCallbackCount != null) inflightCallbackCount.increment();
try {
executor.execute(
() -> {
try {
task.run();
} catch (Exception e) {
Log.e(LOG_TAG, "Exception thrown from observation task", e);
} finally {
if (inflightCallbackCount != null) inflightCallbackCount.decrement();
}
});
} catch (RejectedExecutionException failException) {
if (inflightCallbackCount != null) inflightCallbackCount.decrement();
Log.e(
CronetUrlRequestContext.LOG_TAG,
"Exception posting task to executor",
failException);
}
}
private static void postObservationTaskToExecutor(Executor executor, Runnable task) {
postObservationTaskToExecutor(executor, task, null);
}
public boolean isNetworkThread(Thread thread) {
return thread == mNetworkThread;
}
// Native methods are implemented in cronet_url_request_context_adapter.cc.
@NativeMethods
interface Natives {
long createRequestContextConfig(byte[] serializedRequestContextConfigOptions);
void addQuicHint(long urlRequestContextConfig, String host, int port, int alternatePort);
void addPkp(
long urlRequestContextConfig,
String host,
byte[][] hashes,
boolean includeSubdomains,
long expirationTime);
long createRequestContextAdapter(long urlRequestContextConfig);
byte[] getHistogramDeltas();
@NativeClassQualifiedName("CronetContextAdapter")
void destroy(long nativePtr, CronetUrlRequestContext caller);
@NativeClassQualifiedName("CronetContextAdapter")
boolean startNetLogToFile(
long nativePtr, CronetUrlRequestContext caller, String fileName, boolean logAll);
@NativeClassQualifiedName("CronetContextAdapter")
void startNetLogToDisk(
long nativePtr,
CronetUrlRequestContext caller,
String dirPath,
boolean logAll,
int maxSize);
@NativeClassQualifiedName("CronetContextAdapter")
void stopNetLog(long nativePtr, CronetUrlRequestContext caller);
@NativeClassQualifiedName("CronetContextAdapter")
void initRequestContextOnInitThread(long nativePtr, CronetUrlRequestContext caller);
@NativeClassQualifiedName("CronetContextAdapter")
void configureNetworkQualityEstimatorForTesting(
long nativePtr,
CronetUrlRequestContext caller,
boolean useLocalHostRequests,
boolean useSmallerResponses,
boolean disableOfflineCheck);
@NativeClassQualifiedName("CronetContextAdapter")
void provideRTTObservations(long nativePtr, CronetUrlRequestContext caller, boolean should);
@NativeClassQualifiedName("CronetContextAdapter")
void provideThroughputObservations(
long nativePtr, CronetUrlRequestContext caller, boolean should);
@NativeClassQualifiedName("CronetContextAdapter")
boolean getEnableTelemetry(long nativePtr, CronetUrlRequestContext caller);
}
}