Improve user-agent configuration

- Support setting the user-agent in CronetDataSource
- Support setting the default user-agent in CronetEngineWrapper
- Use the underlying network stack's default user-agent by
  default. Many applications will configure the underlying
  CronetEngine or OkHttpClient with a user-agent that they
  expect to be used throughout their app, so always overriding
  this with our own default, on reflection, is not the best
  thing to do!

Issue: #8395
PiperOrigin-RevId: 350921963
This commit is contained in:
olly 2021-01-09 14:16:51 +00:00 committed by Ian Baker
parent 7e295cbfc9
commit 2a5f6d8f62
14 changed files with 143 additions and 82 deletions

View File

@ -99,6 +99,8 @@
([#1807](https://github.com/google/ExoPlayer/issues/1807)). ([#1807](https://github.com/google/ExoPlayer/issues/1807)).
* Metadata retriever: * Metadata retriever:
* Parse Google Photos HEIC motion photos metadata. * Parse Google Photos HEIC motion photos metadata.
* Data sources:
* Use the user agent of the underlying network stack by default.
* IMA extension: * IMA extension:
* Add support for playback of ads in playlists * Add support for playback of ads in playlists
([#3750](https://github.com/google/ExoPlayer/issues/3750)). ([#3750](https://github.com/google/ExoPlayer/issues/3750)).
@ -116,6 +118,8 @@
* Add `OkHttpDataSource.Factory` and deprecate `OkHttpDataSourceFactory`. * Add `OkHttpDataSource.Factory` and deprecate `OkHttpDataSourceFactory`.
* Cronet extension: * Cronet extension:
* Add `CronetDataSource.Factory` and deprecate `CronetDataSourceFactory`. * Add `CronetDataSource.Factory` and deprecate `CronetDataSourceFactory`.
* Support setting the user agent on `CronetDataSource.Factory` and
`CronetEngineWrapper`.
* Media2 extension * Media2 extension
* Make media2-extension depend on AndroidX media2:media2-session:1.1.0 to * Make media2-extension depend on AndroidX media2:media2-session:1.1.0 to
fix a deadlock while creating PlaybackStateCompat internally. fix a deadlock while creating PlaybackStateCompat internally.

View File

@ -16,7 +16,9 @@
package com.google.android.exoplayer2.demo; package com.google.android.exoplayer2.demo;
import android.content.Context; import android.content.Context;
import android.os.Build;
import com.google.android.exoplayer2.DefaultRenderersFactory; import com.google.android.exoplayer2.DefaultRenderersFactory;
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
import com.google.android.exoplayer2.RenderersFactory; import com.google.android.exoplayer2.RenderersFactory;
import com.google.android.exoplayer2.database.DatabaseProvider; import com.google.android.exoplayer2.database.DatabaseProvider;
import com.google.android.exoplayer2.database.ExoDatabaseProvider; import com.google.android.exoplayer2.database.ExoDatabaseProvider;
@ -44,6 +46,13 @@ public final class DemoUtil {
public static final String DOWNLOAD_NOTIFICATION_CHANNEL_ID = "download_channel"; public static final String DOWNLOAD_NOTIFICATION_CHANNEL_ID = "download_channel";
private static final String USER_AGENT =
"ExoPlayerDemo/"
+ ExoPlayerLibraryInfo.VERSION
+ " (Linux; Android "
+ Build.VERSION.RELEASE
+ ") "
+ ExoPlayerLibraryInfo.VERSION_SLASHY;
private static final String TAG = "DemoUtil"; private static final String TAG = "DemoUtil";
private static final String DOWNLOAD_ACTION_FILE = "actions"; private static final String DOWNLOAD_ACTION_FILE = "actions";
private static final String DOWNLOAD_TRACKER_ACTION_FILE = "tracked_actions"; private static final String DOWNLOAD_TRACKER_ACTION_FILE = "tracked_actions";
@ -79,7 +88,8 @@ public final class DemoUtil {
public static synchronized HttpDataSource.Factory getHttpDataSourceFactory(Context context) { public static synchronized HttpDataSource.Factory getHttpDataSourceFactory(Context context) {
if (httpDataSourceFactory == null) { if (httpDataSourceFactory == null) {
context = context.getApplicationContext(); context = context.getApplicationContext();
CronetEngineWrapper cronetEngineWrapper = new CronetEngineWrapper(context); CronetEngineWrapper cronetEngineWrapper =
new CronetEngineWrapper(context, USER_AGENT, /* preferGMSCoreCronet= */ false);
httpDataSourceFactory = httpDataSourceFactory =
new CronetDataSource.Factory(cronetEngineWrapper, Executors.newSingleThreadExecutor()); new CronetDataSource.Factory(cronetEngineWrapper, Executors.newSingleThreadExecutor());
} }

View File

@ -83,6 +83,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
@Nullable private HttpDataSource.Factory fallbackFactory; @Nullable private HttpDataSource.Factory fallbackFactory;
@Nullable private Predicate<String> contentTypePredicate; @Nullable private Predicate<String> contentTypePredicate;
@Nullable private TransferListener transferListener; @Nullable private TransferListener transferListener;
@Nullable private String userAgent;
private int connectTimeoutMs; private int connectTimeoutMs;
private int readTimeoutMs; private int readTimeoutMs;
private boolean resetTimeoutOnRedirects; private boolean resetTimeoutOnRedirects;
@ -121,6 +122,22 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
return this; return this;
} }
/**
* Sets the user agent that will be used.
*
* <p>The default is {@code null}, which causes the default user agent of the underlying {@link
* CronetEngine} to be used.
*
* @param userAgent The user agent that will be used, or {@code null} to use the default user
* agent of the underlying {@link CronetEngine}.
* @return This factory.
*/
public Factory setUserAgent(@Nullable String userAgent) {
this.userAgent = userAgent;
internalFallbackFactory.setUserAgent(userAgent);
return this;
}
/** /**
* Sets the connect timeout, in milliseconds. * Sets the connect timeout, in milliseconds.
* *
@ -239,6 +256,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
readTimeoutMs, readTimeoutMs,
resetTimeoutOnRedirects, resetTimeoutOnRedirects,
handleSetCookieRequests, handleSetCookieRequests,
userAgent,
defaultRequestProperties, defaultRequestProperties,
contentTypePredicate); contentTypePredicate);
if (transferListener != null) { if (transferListener != null) {
@ -293,6 +311,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
private final int readTimeoutMs; private final int readTimeoutMs;
private final boolean resetTimeoutOnRedirects; private final boolean resetTimeoutOnRedirects;
private final boolean handleSetCookieRequests; private final boolean handleSetCookieRequests;
@Nullable private final String userAgent;
@Nullable private final RequestProperties defaultRequestProperties; @Nullable private final RequestProperties defaultRequestProperties;
private final RequestProperties requestProperties; private final RequestProperties requestProperties;
private final ConditionVariable operation; private final ConditionVariable operation;
@ -352,6 +371,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
readTimeoutMs, readTimeoutMs,
resetTimeoutOnRedirects, resetTimeoutOnRedirects,
/* handleSetCookieRequests= */ false, /* handleSetCookieRequests= */ false,
/* userAgent= */ null,
defaultRequestProperties, defaultRequestProperties,
/* contentTypePredicate= */ null); /* contentTypePredicate= */ null);
} }
@ -373,6 +393,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
readTimeoutMs, readTimeoutMs,
resetTimeoutOnRedirects, resetTimeoutOnRedirects,
handleSetCookieRequests, handleSetCookieRequests,
/* userAgent= */ null,
defaultRequestProperties, defaultRequestProperties,
/* contentTypePredicate= */ null); /* contentTypePredicate= */ null);
} }
@ -434,6 +455,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
readTimeoutMs, readTimeoutMs,
resetTimeoutOnRedirects, resetTimeoutOnRedirects,
handleSetCookieRequests, handleSetCookieRequests,
/* userAgent= */ null,
defaultRequestProperties, defaultRequestProperties,
contentTypePredicate); contentTypePredicate);
} }
@ -445,6 +467,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
int readTimeoutMs, int readTimeoutMs,
boolean resetTimeoutOnRedirects, boolean resetTimeoutOnRedirects,
boolean handleSetCookieRequests, boolean handleSetCookieRequests,
@Nullable String userAgent,
@Nullable RequestProperties defaultRequestProperties, @Nullable RequestProperties defaultRequestProperties,
@Nullable Predicate<String> contentTypePredicate) { @Nullable Predicate<String> contentTypePredicate) {
super(/* isNetwork= */ true); super(/* isNetwork= */ true);
@ -453,8 +476,9 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
this.connectTimeoutMs = connectTimeoutMs; this.connectTimeoutMs = connectTimeoutMs;
this.readTimeoutMs = readTimeoutMs; this.readTimeoutMs = readTimeoutMs;
this.resetTimeoutOnRedirects = resetTimeoutOnRedirects; this.resetTimeoutOnRedirects = resetTimeoutOnRedirects;
this.defaultRequestProperties = defaultRequestProperties;
this.handleSetCookieRequests = handleSetCookieRequests; this.handleSetCookieRequests = handleSetCookieRequests;
this.userAgent = userAgent;
this.defaultRequestProperties = defaultRequestProperties;
this.contentTypePredicate = contentTypePredicate; this.contentTypePredicate = contentTypePredicate;
clock = Clock.DEFAULT; clock = Clock.DEFAULT;
urlRequestCallback = new UrlRequestCallback(); urlRequestCallback = new UrlRequestCallback();
@ -820,6 +844,9 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
} }
requestBuilder.addHeader("Range", rangeValue.toString()); requestBuilder.addHeader("Range", rangeValue.toString());
} }
if (userAgent != null) {
requestBuilder.addHeader("User-Agent", userAgent);
}
// TODO: Uncomment when https://bugs.chromium.org/p/chromium/issues/detail?id=711810 is fixed // TODO: Uncomment when https://bugs.chromium.org/p/chromium/issues/detail?id=711810 is fixed
// (adjusting the code as necessary). // (adjusting the code as necessary).
// Force identity encoding unless gzip is allowed. // Force identity encoding unless gzip is allowed.

View File

@ -15,7 +15,6 @@
*/ */
package com.google.android.exoplayer2.ext.cronet; package com.google.android.exoplayer2.ext.cronet;
import static com.google.android.exoplayer2.ExoPlayerLibraryInfo.DEFAULT_USER_AGENT;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory; import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
@ -92,7 +91,7 @@ public final class CronetDataSourceFactory extends BaseFactory {
* @param executor The {@link java.util.concurrent.Executor} that will perform the requests. * @param executor The {@link java.util.concurrent.Executor} that will perform the requests.
*/ */
public CronetDataSourceFactory(CronetEngineWrapper cronetEngineWrapper, Executor executor) { public CronetDataSourceFactory(CronetEngineWrapper cronetEngineWrapper, Executor executor) {
this(cronetEngineWrapper, executor, DEFAULT_USER_AGENT); this(cronetEngineWrapper, executor, /* userAgent= */ (String) null);
} }
/** /**
@ -106,10 +105,12 @@ public final class CronetDataSourceFactory extends BaseFactory {
* *
* @param cronetEngineWrapper A {@link CronetEngineWrapper}. * @param cronetEngineWrapper A {@link CronetEngineWrapper}.
* @param executor The {@link java.util.concurrent.Executor} that will perform the requests. * @param executor The {@link java.util.concurrent.Executor} that will perform the requests.
* @param userAgent A user agent used to create a fallback HttpDataSource if needed. * @param userAgent The user agent that will be used by the fallback {@link HttpDataSource} if
* needed, or {@code null} for the fallback to use the default user agent of the underlying
* platform.
*/ */
public CronetDataSourceFactory( public CronetDataSourceFactory(
CronetEngineWrapper cronetEngineWrapper, Executor executor, String userAgent) { CronetEngineWrapper cronetEngineWrapper, Executor executor, @Nullable String userAgent) {
this( this(
cronetEngineWrapper, cronetEngineWrapper,
executor, executor,
@ -136,7 +137,9 @@ public final class CronetDataSourceFactory extends BaseFactory {
* @param connectTimeoutMs The connection timeout, in milliseconds. * @param connectTimeoutMs The connection timeout, in milliseconds.
* @param readTimeoutMs The read timeout, in milliseconds. * @param readTimeoutMs The read timeout, in milliseconds.
* @param resetTimeoutOnRedirects Whether the connect timeout is reset when a redirect occurs. * @param resetTimeoutOnRedirects Whether the connect timeout is reset when a redirect occurs.
* @param userAgent A user agent used to create a fallback HttpDataSource if needed. * @param userAgent The user agent that will be used by the fallback {@link HttpDataSource} if
* needed, or {@code null} for the fallback to use the default user agent of the underlying
* platform.
*/ */
public CronetDataSourceFactory( public CronetDataSourceFactory(
CronetEngineWrapper cronetEngineWrapper, CronetEngineWrapper cronetEngineWrapper,
@ -144,7 +147,7 @@ public final class CronetDataSourceFactory extends BaseFactory {
int connectTimeoutMs, int connectTimeoutMs,
int readTimeoutMs, int readTimeoutMs,
boolean resetTimeoutOnRedirects, boolean resetTimeoutOnRedirects,
String userAgent) { @Nullable String userAgent) {
this( this(
cronetEngineWrapper, cronetEngineWrapper,
executor, executor,
@ -238,7 +241,7 @@ public final class CronetDataSourceFactory extends BaseFactory {
CronetEngineWrapper cronetEngineWrapper, CronetEngineWrapper cronetEngineWrapper,
Executor executor, Executor executor,
@Nullable TransferListener transferListener) { @Nullable TransferListener transferListener) {
this(cronetEngineWrapper, executor, transferListener, DEFAULT_USER_AGENT); this(cronetEngineWrapper, executor, transferListener, /* userAgent= */ (String) null);
} }
/** /**
@ -253,13 +256,15 @@ public final class CronetDataSourceFactory extends BaseFactory {
* @param cronetEngineWrapper A {@link CronetEngineWrapper}. * @param cronetEngineWrapper A {@link CronetEngineWrapper}.
* @param executor The {@link java.util.concurrent.Executor} that will perform the requests. * @param executor The {@link java.util.concurrent.Executor} that will perform the requests.
* @param transferListener An optional listener. * @param transferListener An optional listener.
* @param userAgent A user agent used to create a fallback HttpDataSource if needed. * @param userAgent The user agent that will be used by the fallback {@link HttpDataSource} if
* needed, or {@code null} for the fallback to use the default user agent of the underlying
* platform.
*/ */
public CronetDataSourceFactory( public CronetDataSourceFactory(
CronetEngineWrapper cronetEngineWrapper, CronetEngineWrapper cronetEngineWrapper,
Executor executor, Executor executor,
@Nullable TransferListener transferListener, @Nullable TransferListener transferListener,
String userAgent) { @Nullable String userAgent) {
this( this(
cronetEngineWrapper, cronetEngineWrapper,
executor, executor,
@ -287,7 +292,9 @@ public final class CronetDataSourceFactory extends BaseFactory {
* @param connectTimeoutMs The connection timeout, in milliseconds. * @param connectTimeoutMs The connection timeout, in milliseconds.
* @param readTimeoutMs The read timeout, in milliseconds. * @param readTimeoutMs The read timeout, in milliseconds.
* @param resetTimeoutOnRedirects Whether the connect timeout is reset when a redirect occurs. * @param resetTimeoutOnRedirects Whether the connect timeout is reset when a redirect occurs.
* @param userAgent A user agent used to create a fallback HttpDataSource if needed. * @param userAgent The user agent that will be used by the fallback {@link HttpDataSource} if
* needed, or {@code null} for the fallback to use the default user agent of the underlying
* platform.
*/ */
public CronetDataSourceFactory( public CronetDataSourceFactory(
CronetEngineWrapper cronetEngineWrapper, CronetEngineWrapper cronetEngineWrapper,
@ -296,7 +303,7 @@ public final class CronetDataSourceFactory extends BaseFactory {
int connectTimeoutMs, int connectTimeoutMs,
int readTimeoutMs, int readTimeoutMs,
boolean resetTimeoutOnRedirects, boolean resetTimeoutOnRedirects,
String userAgent) { @Nullable String userAgent) {
this( this(
cronetEngineWrapper, cronetEngineWrapper,
executor, executor,

View File

@ -73,25 +73,29 @@ public final class CronetEngineWrapper {
public static final int SOURCE_UNAVAILABLE = 4; public static final int SOURCE_UNAVAILABLE = 4;
/** /**
* Creates a wrapper for a {@link CronetEngine} which automatically selects the most suitable * Creates a wrapper for a {@link CronetEngine} built using the most suitable {@link
* {@link CronetProvider}. Sets wrapper to prefer natively bundled Cronet over GMSCore Cronet * CronetProvider}. When natively bundled Cronet and GMSCore Cronet are both available, the
* if both are available. * natively bundled provider is preferred.
* *
* @param context A context. * @param context A context.
*/ */
public CronetEngineWrapper(Context context) { public CronetEngineWrapper(Context context) {
this(context, false); this(context, /* userAgent= */ null, /* preferGMSCoreCronet= */ false);
} }
/** /**
* Creates a wrapper for a {@link CronetEngine} which automatically selects the most suitable * Creates a wrapper for a {@link CronetEngine} built using the most suitable {@link
* {@link CronetProvider} based on user preference. * CronetProvider}. When natively bundled Cronet and GMSCore Cronet are both available, {@code
* preferGMSCoreCronet} determines which is preferred.
* *
* @param context A context. * @param context A context.
* @param userAgent A default user agent, or {@code null} to use a default user agent of the
* {@link CronetEngine}.
* @param preferGMSCoreCronet Whether Cronet from GMSCore should be preferred over natively * @param preferGMSCoreCronet Whether Cronet from GMSCore should be preferred over natively
* bundled Cronet if both are available. * bundled Cronet if both are available.
*/ */
public CronetEngineWrapper(Context context, boolean preferGMSCoreCronet) { public CronetEngineWrapper(
Context context, @Nullable String userAgent, boolean preferGMSCoreCronet) {
CronetEngine cronetEngine = null; CronetEngine cronetEngine = null;
@CronetEngineSource int cronetEngineSource = SOURCE_UNAVAILABLE; @CronetEngineSource int cronetEngineSource = SOURCE_UNAVAILABLE;
List<CronetProvider> cronetProviders = new ArrayList<>(CronetProvider.getAllProviders(context)); List<CronetProvider> cronetProviders = new ArrayList<>(CronetProvider.getAllProviders(context));
@ -108,7 +112,11 @@ public final class CronetEngineWrapper {
for (int i = 0; i < cronetProviders.size() && cronetEngine == null; i++) { for (int i = 0; i < cronetProviders.size() && cronetEngine == null; i++) {
String providerName = cronetProviders.get(i).getName(); String providerName = cronetProviders.get(i).getName();
try { try {
cronetEngine = cronetProviders.get(i).createBuilder().build(); CronetEngine.Builder cronetEngineBuilder = cronetProviders.get(i).createBuilder();
if (userAgent != null) {
cronetEngineBuilder.setUserAgent(userAgent);
}
cronetEngine = cronetEngineBuilder.build();
if (providerComparator.isNativeProvider(providerName)) { if (providerComparator.isNativeProvider(providerName)) {
cronetEngineSource = SOURCE_NATIVE; cronetEngineSource = SOURCE_NATIVE;
} else if (providerComparator.isGMSCoreProvider(providerName)) { } else if (providerComparator.isGMSCoreProvider(providerName)) {
@ -133,9 +141,9 @@ public final class CronetEngineWrapper {
} }
/** /**
* Creates a wrapper for an existing CronetEngine. * Creates a wrapper for an existing {@link CronetEngine}.
* *
* @param cronetEngine An existing CronetEngine. * @param cronetEngine The CronetEngine to wrap.
*/ */
public CronetEngineWrapper(CronetEngine cronetEngine) { public CronetEngineWrapper(CronetEngine cronetEngine) {
this.cronetEngine = cronetEngine; this.cronetEngine = cronetEngine;

View File

@ -36,7 +36,6 @@ import android.os.SystemClock;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource; import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
import com.google.android.exoplayer2.upstream.HttpDataSource; import com.google.android.exoplayer2.upstream.HttpDataSource;
@ -1412,8 +1411,6 @@ public final class CronetDataSourceTest {
dataSourceUnderTest.open(dataSpec); dataSourceUnderTest.open(dataSpec);
Headers headers = mockWebServer.takeRequest(10, SECONDS).getHeaders();
assertThat(headers.get("user-agent")).isEqualTo(ExoPlayerLibraryInfo.DEFAULT_USER_AGENT);
verify(mockTransferListener) verify(mockTransferListener)
.onTransferInitializing(eq(dataSourceUnderTest), eq(dataSpec), /* isNetwork= */ eq(true)); .onTransferInitializing(eq(dataSourceUnderTest), eq(dataSpec), /* isNetwork= */ eq(true));
verify(mockTransferListener) verify(mockTransferListener)

View File

@ -15,7 +15,6 @@
*/ */
package com.google.android.exoplayer2.ext.okhttp; package com.google.android.exoplayer2.ext.okhttp;
import static com.google.android.exoplayer2.ExoPlayerLibraryInfo.DEFAULT_USER_AGENT;
import static com.google.android.exoplayer2.util.Util.castNonNull; import static com.google.android.exoplayer2.util.Util.castNonNull;
import static java.lang.Math.min; import static java.lang.Math.min;
@ -83,7 +82,6 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource {
public Factory(Call.Factory callFactory) { public Factory(Call.Factory callFactory) {
this.callFactory = callFactory; this.callFactory = callFactory;
defaultRequestProperties = new RequestProperties(); defaultRequestProperties = new RequestProperties();
userAgent = DEFAULT_USER_AGENT;
} }
/** @deprecated Use {@link #setDefaultRequestProperties(Map)} instead. */ /** @deprecated Use {@link #setDefaultRequestProperties(Map)} instead. */
@ -102,12 +100,14 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource {
/** /**
* Sets the user agent that will be used. * Sets the user agent that will be used.
* *
* <p>The default is {@link ExoPlayerLibraryInfo#DEFAULT_USER_AGENT}. * <p>The default is {@code null}, which causes the default user agent of the underlying {@link
* OkHttpClient} to be used.
* *
* @param userAgent The user agent that will be used. * @param userAgent The user agent that will be used, or {@code null} to use the default user
* agent of the underlying {@link OkHttpClient}.
* @return This factory. * @return This factory.
*/ */
public Factory setUserAgent(String userAgent) { public Factory setUserAgent(@Nullable String userAgent) {
this.userAgent = userAgent; this.userAgent = userAgent;
return this; return this;
} }
@ -193,7 +193,7 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource {
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
@Deprecated @Deprecated
public OkHttpDataSource(Call.Factory callFactory) { public OkHttpDataSource(Call.Factory callFactory) {
this(callFactory, DEFAULT_USER_AGENT); this(callFactory, /* userAgent= */ null);
} }
/** @deprecated Use {@link OkHttpDataSource.Factory} instead. */ /** @deprecated Use {@link OkHttpDataSource.Factory} instead. */

View File

@ -15,7 +15,6 @@
*/ */
package com.google.android.exoplayer2.ext.okhttp; package com.google.android.exoplayer2.ext.okhttp;
import static com.google.android.exoplayer2.ExoPlayerLibraryInfo.DEFAULT_USER_AGENT;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.upstream.HttpDataSource; import com.google.android.exoplayer2.upstream.HttpDataSource;
@ -40,7 +39,7 @@ public final class OkHttpDataSourceFactory extends BaseFactory {
* by the sources created by the factory. * by the sources created by the factory.
*/ */
public OkHttpDataSourceFactory(Call.Factory callFactory) { public OkHttpDataSourceFactory(Call.Factory callFactory) {
this(callFactory, DEFAULT_USER_AGENT, /* listener= */ null, /* cacheControl= */ null); this(callFactory, /* userAgent= */ null, /* listener= */ null, /* cacheControl= */ null);
} }
/** /**

View File

@ -46,9 +46,14 @@ public final class ExoPlayerLibraryInfo {
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa. // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
public static final int VERSION_INT = 2012002; public static final int VERSION_INT = 2012002;
/** The default user agent for requests made by the library. */ /**
* The default user agent for requests made by the library.
*
* @deprecated ExoPlayer now uses the user agent of the underlying network stack by default.
*/
@Deprecated
public static final String DEFAULT_USER_AGENT = public static final String DEFAULT_USER_AGENT =
VERSION_SLASHY + " (Linux;Android " + Build.VERSION.RELEASE + ") " + VERSION_SLASHY; VERSION_SLASHY + " (Linux; Android " + Build.VERSION.RELEASE + ") " + VERSION_SLASHY;
/** /**
* Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions} * Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions}

View File

@ -15,7 +15,6 @@
*/ */
package com.google.android.exoplayer2.source; package com.google.android.exoplayer2.source;
import static com.google.android.exoplayer2.ExoPlayerLibraryInfo.DEFAULT_USER_AGENT;
import static com.google.android.exoplayer2.drm.DefaultDrmSessionManager.MODE_PLAYBACK; import static com.google.android.exoplayer2.drm.DefaultDrmSessionManager.MODE_PLAYBACK;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@ -24,6 +23,7 @@ import com.google.android.exoplayer2.drm.DefaultDrmSessionManager;
import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.FrameworkMediaDrm; import com.google.android.exoplayer2.drm.FrameworkMediaDrm;
import com.google.android.exoplayer2.drm.HttpMediaDrmCallback; import com.google.android.exoplayer2.drm.HttpMediaDrmCallback;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory; import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
import com.google.android.exoplayer2.upstream.HttpDataSource; import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
@ -73,7 +73,7 @@ public final class MediaSourceDrmHelper {
HttpDataSource.Factory dataSourceFactory = HttpDataSource.Factory dataSourceFactory =
drmHttpDataSourceFactory != null drmHttpDataSourceFactory != null
? drmHttpDataSourceFactory ? drmHttpDataSourceFactory
: new DefaultHttpDataSourceFactory(userAgent != null ? userAgent : DEFAULT_USER_AGENT); : new DefaultHttpDataSource.Factory().setUserAgent(userAgent);
HttpMediaDrmCallback httpDrmCallback = HttpMediaDrmCallback httpDrmCallback =
new HttpMediaDrmCallback( new HttpMediaDrmCallback(
drmConfiguration.licenseUri == null ? null : drmConfiguration.licenseUri.toString(), drmConfiguration.licenseUri == null ? null : drmConfiguration.licenseUri.toString(),

View File

@ -19,7 +19,6 @@ import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.net.Uri; import android.net.Uri;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
@ -89,7 +88,7 @@ public final class DefaultDataSource implements DataSource {
public DefaultDataSource(Context context, boolean allowCrossProtocolRedirects) { public DefaultDataSource(Context context, boolean allowCrossProtocolRedirects) {
this( this(
context, context,
ExoPlayerLibraryInfo.DEFAULT_USER_AGENT, /* userAgent= */ null,
DefaultHttpDataSource.DEFAULT_CONNECT_TIMEOUT_MILLIS, DefaultHttpDataSource.DEFAULT_CONNECT_TIMEOUT_MILLIS,
DefaultHttpDataSource.DEFAULT_READ_TIMEOUT_MILLIS, DefaultHttpDataSource.DEFAULT_READ_TIMEOUT_MILLIS,
allowCrossProtocolRedirects); allowCrossProtocolRedirects);
@ -99,11 +98,13 @@ public final class DefaultDataSource implements DataSource {
* Constructs a new instance, optionally configured to follow cross-protocol redirects. * Constructs a new instance, optionally configured to follow cross-protocol redirects.
* *
* @param context A context. * @param context A context.
* @param userAgent The User-Agent to use when requesting remote data. * @param userAgent The user agent that will be used when requesting remote data, or {@code null}
* to use the default user agent of the underlying platform.
* @param allowCrossProtocolRedirects Whether cross-protocol redirects (i.e. redirects from HTTP * @param allowCrossProtocolRedirects Whether cross-protocol redirects (i.e. redirects from HTTP
* to HTTPS and vice versa) are enabled when fetching remote data. * to HTTPS and vice versa) are enabled when fetching remote data.
*/ */
public DefaultDataSource(Context context, String userAgent, boolean allowCrossProtocolRedirects) { public DefaultDataSource(
Context context, @Nullable String userAgent, boolean allowCrossProtocolRedirects) {
this( this(
context, context,
userAgent, userAgent,
@ -116,7 +117,8 @@ public final class DefaultDataSource implements DataSource {
* Constructs a new instance, optionally configured to follow cross-protocol redirects. * Constructs a new instance, optionally configured to follow cross-protocol redirects.
* *
* @param context A context. * @param context A context.
* @param userAgent The User-Agent to use when requesting remote data. * @param userAgent The user agent that will be used when requesting remote data, or {@code null}
* to use the default user agent of the underlying platform.
* @param connectTimeoutMillis The connection timeout that should be used when requesting remote * @param connectTimeoutMillis The connection timeout that should be used when requesting remote
* data, in milliseconds. A timeout of zero is interpreted as an infinite timeout. * data, in milliseconds. A timeout of zero is interpreted as an infinite timeout.
* @param readTimeoutMillis The read timeout that should be used when requesting remote data, in * @param readTimeoutMillis The read timeout that should be used when requesting remote data, in
@ -126,7 +128,7 @@ public final class DefaultDataSource implements DataSource {
*/ */
public DefaultDataSource( public DefaultDataSource(
Context context, Context context,
String userAgent, @Nullable String userAgent,
int connectTimeoutMillis, int connectTimeoutMillis,
int readTimeoutMillis, int readTimeoutMillis,
boolean allowCrossProtocolRedirects) { boolean allowCrossProtocolRedirects) {

View File

@ -15,8 +15,6 @@
*/ */
package com.google.android.exoplayer2.upstream; package com.google.android.exoplayer2.upstream;
import static com.google.android.exoplayer2.ExoPlayerLibraryInfo.DEFAULT_USER_AGENT;
import android.content.Context; import android.content.Context;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.upstream.DataSource.Factory; import com.google.android.exoplayer2.upstream.DataSource.Factory;
@ -37,16 +35,17 @@ public final class DefaultDataSourceFactory implements Factory {
* @param context A context. * @param context A context.
*/ */
public DefaultDataSourceFactory(Context context) { public DefaultDataSourceFactory(Context context) {
this(context, DEFAULT_USER_AGENT, /* listener= */ null); this(context, /* userAgent= */ (String) null, /* listener= */ null);
} }
/** /**
* Creates an instance. * Creates an instance.
* *
* @param context A context. * @param context A context.
* @param userAgent The User-Agent string that should be used. * @param userAgent The user agent that will be used when requesting remote data, or {@code null}
* to use the default user agent of the underlying platform.
*/ */
public DefaultDataSourceFactory(Context context, String userAgent) { public DefaultDataSourceFactory(Context context, @Nullable String userAgent) {
this(context, userAgent, /* listener= */ null); this(context, userAgent, /* listener= */ null);
} }
@ -54,11 +53,12 @@ public final class DefaultDataSourceFactory implements Factory {
* Creates an instance. * Creates an instance.
* *
* @param context A context. * @param context A context.
* @param userAgent The User-Agent string that should be used. * @param userAgent The user agent that will be used when requesting remote data, or {@code null}
* to use the default user agent of the underlying platform.
* @param listener An optional listener. * @param listener An optional listener.
*/ */
public DefaultDataSourceFactory( public DefaultDataSourceFactory(
Context context, String userAgent, @Nullable TransferListener listener) { Context context, @Nullable String userAgent, @Nullable TransferListener listener) {
this(context, listener, new DefaultHttpDataSource.Factory().setUserAgent(userAgent)); this(context, listener, new DefaultHttpDataSource.Factory().setUserAgent(userAgent));
} }

View File

@ -15,7 +15,6 @@
*/ */
package com.google.android.exoplayer2.upstream; package com.google.android.exoplayer2.upstream;
import static com.google.android.exoplayer2.ExoPlayerLibraryInfo.DEFAULT_USER_AGENT;
import static java.lang.Math.max; import static java.lang.Math.max;
import static java.lang.Math.min; import static java.lang.Math.min;
@ -24,7 +23,6 @@ import android.text.TextUtils;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
import com.google.android.exoplayer2.upstream.DataSpec.HttpMethod; import com.google.android.exoplayer2.upstream.DataSpec.HttpMethod;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Log;
@ -69,7 +67,7 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
@Nullable private TransferListener transferListener; @Nullable private TransferListener transferListener;
@Nullable private Predicate<String> contentTypePredicate; @Nullable private Predicate<String> contentTypePredicate;
private String userAgent; @Nullable private String userAgent;
private int connectTimeoutMs; private int connectTimeoutMs;
private int readTimeoutMs; private int readTimeoutMs;
private boolean allowCrossProtocolRedirects; private boolean allowCrossProtocolRedirects;
@ -77,7 +75,6 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
/** Creates an instance. */ /** Creates an instance. */
public Factory() { public Factory() {
defaultRequestProperties = new RequestProperties(); defaultRequestProperties = new RequestProperties();
userAgent = DEFAULT_USER_AGENT;
connectTimeoutMs = DEFAULT_CONNECT_TIMEOUT_MILLIS; connectTimeoutMs = DEFAULT_CONNECT_TIMEOUT_MILLIS;
readTimeoutMs = DEFAULT_READ_TIMEOUT_MILLIS; readTimeoutMs = DEFAULT_READ_TIMEOUT_MILLIS;
} }
@ -98,12 +95,14 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
/** /**
* Sets the user agent that will be used. * Sets the user agent that will be used.
* *
* <p>The default is {@link ExoPlayerLibraryInfo#DEFAULT_USER_AGENT}. * <p>The default is {@code null}, which causes the default user agent of the underlying
* platform to be used.
* *
* @param userAgent The user agent that will be used. * @param userAgent The user agent that will be used, or {@code null} to use the default user
* agent of the underlying platform.
* @return This factory. * @return This factory.
*/ */
public Factory setUserAgent(String userAgent) { public Factory setUserAgent(@Nullable String userAgent) {
this.userAgent = userAgent; this.userAgent = userAgent;
return this; return this;
} }
@ -214,7 +213,7 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
private final boolean allowCrossProtocolRedirects; private final boolean allowCrossProtocolRedirects;
private final int connectTimeoutMillis; private final int connectTimeoutMillis;
private final int readTimeoutMillis; private final int readTimeoutMillis;
private final String userAgent; @Nullable private final String userAgent;
@Nullable private final RequestProperties defaultRequestProperties; @Nullable private final RequestProperties defaultRequestProperties;
private final RequestProperties requestProperties; private final RequestProperties requestProperties;
@ -235,23 +234,21 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
@Deprecated @Deprecated
public DefaultHttpDataSource() { public DefaultHttpDataSource() {
this( this(/* userAgent= */ null, DEFAULT_CONNECT_TIMEOUT_MILLIS, DEFAULT_READ_TIMEOUT_MILLIS);
ExoPlayerLibraryInfo.DEFAULT_USER_AGENT,
DEFAULT_CONNECT_TIMEOUT_MILLIS,
DEFAULT_READ_TIMEOUT_MILLIS);
} }
/** @deprecated Use {@link DefaultHttpDataSource.Factory} instead. */ /** @deprecated Use {@link DefaultHttpDataSource.Factory} instead. */
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
@Deprecated @Deprecated
public DefaultHttpDataSource(String userAgent) { public DefaultHttpDataSource(@Nullable String userAgent) {
this(userAgent, DEFAULT_CONNECT_TIMEOUT_MILLIS, DEFAULT_READ_TIMEOUT_MILLIS); this(userAgent, DEFAULT_CONNECT_TIMEOUT_MILLIS, DEFAULT_READ_TIMEOUT_MILLIS);
} }
/** @deprecated Use {@link DefaultHttpDataSource.Factory} instead. */ /** @deprecated Use {@link DefaultHttpDataSource.Factory} instead. */
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
@Deprecated @Deprecated
public DefaultHttpDataSource(String userAgent, int connectTimeoutMillis, int readTimeoutMillis) { public DefaultHttpDataSource(
@Nullable String userAgent, int connectTimeoutMillis, int readTimeoutMillis) {
this( this(
userAgent, userAgent,
connectTimeoutMillis, connectTimeoutMillis,
@ -263,7 +260,7 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
/** @deprecated Use {@link DefaultHttpDataSource.Factory} instead. */ /** @deprecated Use {@link DefaultHttpDataSource.Factory} instead. */
@Deprecated @Deprecated
public DefaultHttpDataSource( public DefaultHttpDataSource(
String userAgent, @Nullable String userAgent,
int connectTimeoutMillis, int connectTimeoutMillis,
int readTimeoutMillis, int readTimeoutMillis,
boolean allowCrossProtocolRedirects, boolean allowCrossProtocolRedirects,
@ -278,14 +275,14 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
} }
private DefaultHttpDataSource( private DefaultHttpDataSource(
String userAgent, @Nullable String userAgent,
int connectTimeoutMillis, int connectTimeoutMillis,
int readTimeoutMillis, int readTimeoutMillis,
boolean allowCrossProtocolRedirects, boolean allowCrossProtocolRedirects,
@Nullable RequestProperties defaultRequestProperties, @Nullable RequestProperties defaultRequestProperties,
@Nullable Predicate<String> contentTypePredicate) { @Nullable Predicate<String> contentTypePredicate) {
super(/* isNetwork= */ true); super(/* isNetwork= */ true);
this.userAgent = Assertions.checkNotEmpty(userAgent); this.userAgent = userAgent;
this.connectTimeoutMillis = connectTimeoutMillis; this.connectTimeoutMillis = connectTimeoutMillis;
this.readTimeoutMillis = readTimeoutMillis; this.readTimeoutMillis = readTimeoutMillis;
this.allowCrossProtocolRedirects = allowCrossProtocolRedirects; this.allowCrossProtocolRedirects = allowCrossProtocolRedirects;
@ -620,7 +617,9 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
} }
connection.setRequestProperty("Range", rangeRequest); connection.setRequestProperty("Range", rangeRequest);
} }
if (userAgent != null) {
connection.setRequestProperty("User-Agent", userAgent); connection.setRequestProperty("User-Agent", userAgent);
}
connection.setRequestProperty("Accept-Encoding", allowGzip ? "gzip" : "identity"); connection.setRequestProperty("Accept-Encoding", allowGzip ? "gzip" : "identity");
connection.setInstanceFollowRedirects(followRedirects); connection.setInstanceFollowRedirects(followRedirects);
connection.setDoOutput(httpBody != null); connection.setDoOutput(httpBody != null);

View File

@ -15,17 +15,15 @@
*/ */
package com.google.android.exoplayer2.upstream; package com.google.android.exoplayer2.upstream;
import static com.google.android.exoplayer2.ExoPlayerLibraryInfo.DEFAULT_USER_AGENT;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.upstream.HttpDataSource.BaseFactory; import com.google.android.exoplayer2.upstream.HttpDataSource.BaseFactory;
import com.google.android.exoplayer2.util.Assertions;
/** @deprecated Use {@link DefaultHttpDataSource.Factory} instead. */ /** @deprecated Use {@link DefaultHttpDataSource.Factory} instead. */
@Deprecated @Deprecated
public final class DefaultHttpDataSourceFactory extends BaseFactory { public final class DefaultHttpDataSourceFactory extends BaseFactory {
private final String userAgent; @Nullable private final String userAgent;
@Nullable private final TransferListener listener; @Nullable private final TransferListener listener;
private final int connectTimeoutMillis; private final int connectTimeoutMillis;
private final int readTimeoutMillis; private final int readTimeoutMillis;
@ -37,7 +35,7 @@ public final class DefaultHttpDataSourceFactory extends BaseFactory {
* timeout and disables cross-protocol redirects. * timeout and disables cross-protocol redirects.
*/ */
public DefaultHttpDataSourceFactory() { public DefaultHttpDataSourceFactory() {
this(DEFAULT_USER_AGENT); this(/* userAgent= */ null);
} }
/** /**
@ -45,9 +43,10 @@ public final class DefaultHttpDataSourceFactory extends BaseFactory {
* connection timeout, {@link DefaultHttpDataSource#DEFAULT_READ_TIMEOUT_MILLIS} as the read * connection timeout, {@link DefaultHttpDataSource#DEFAULT_READ_TIMEOUT_MILLIS} as the read
* timeout and disables cross-protocol redirects. * timeout and disables cross-protocol redirects.
* *
* @param userAgent The User-Agent string that should be used. * @param userAgent The user agent that will be used, or {@code null} to use the default user
* agent of the underlying platform.
*/ */
public DefaultHttpDataSourceFactory(String userAgent) { public DefaultHttpDataSourceFactory(@Nullable String userAgent) {
this(userAgent, null); this(userAgent, null);
} }
@ -56,17 +55,20 @@ public final class DefaultHttpDataSourceFactory extends BaseFactory {
* connection timeout, {@link DefaultHttpDataSource#DEFAULT_READ_TIMEOUT_MILLIS} as the read * connection timeout, {@link DefaultHttpDataSource#DEFAULT_READ_TIMEOUT_MILLIS} as the read
* timeout and disables cross-protocol redirects. * timeout and disables cross-protocol redirects.
* *
* @param userAgent The User-Agent string that should be used. * @param userAgent The user agent that will be used, or {@code null} to use the default user
* agent of the underlying platform.
* @param listener An optional listener. * @param listener An optional listener.
* @see #DefaultHttpDataSourceFactory(String, TransferListener, int, int, boolean) * @see #DefaultHttpDataSourceFactory(String, TransferListener, int, int, boolean)
*/ */
public DefaultHttpDataSourceFactory(String userAgent, @Nullable TransferListener listener) { public DefaultHttpDataSourceFactory(
@Nullable String userAgent, @Nullable TransferListener listener) {
this(userAgent, listener, DefaultHttpDataSource.DEFAULT_CONNECT_TIMEOUT_MILLIS, this(userAgent, listener, DefaultHttpDataSource.DEFAULT_CONNECT_TIMEOUT_MILLIS,
DefaultHttpDataSource.DEFAULT_READ_TIMEOUT_MILLIS, false); DefaultHttpDataSource.DEFAULT_READ_TIMEOUT_MILLIS, false);
} }
/** /**
* @param userAgent The User-Agent string that should be used. * @param userAgent The user agent that will be used, or {@code null} to use the default user
* agent of the underlying platform.
* @param connectTimeoutMillis The connection timeout that should be used when requesting remote * @param connectTimeoutMillis The connection timeout that should be used when requesting remote
* data, in milliseconds. A timeout of zero is interpreted as an infinite timeout. * data, in milliseconds. A timeout of zero is interpreted as an infinite timeout.
* @param readTimeoutMillis The read timeout that should be used when requesting remote data, in * @param readTimeoutMillis The read timeout that should be used when requesting remote data, in
@ -75,7 +77,7 @@ public final class DefaultHttpDataSourceFactory extends BaseFactory {
* to HTTPS and vice versa) are enabled. * to HTTPS and vice versa) are enabled.
*/ */
public DefaultHttpDataSourceFactory( public DefaultHttpDataSourceFactory(
String userAgent, @Nullable String userAgent,
int connectTimeoutMillis, int connectTimeoutMillis,
int readTimeoutMillis, int readTimeoutMillis,
boolean allowCrossProtocolRedirects) { boolean allowCrossProtocolRedirects) {
@ -88,7 +90,8 @@ public final class DefaultHttpDataSourceFactory extends BaseFactory {
} }
/** /**
* @param userAgent The User-Agent string that should be used. * @param userAgent The user agent that will be used, or {@code null} to use the default user
* agent of the underlying platform.
* @param listener An optional listener. * @param listener An optional listener.
* @param connectTimeoutMillis The connection timeout that should be used when requesting remote * @param connectTimeoutMillis The connection timeout that should be used when requesting remote
* data, in milliseconds. A timeout of zero is interpreted as an infinite timeout. * data, in milliseconds. A timeout of zero is interpreted as an infinite timeout.
@ -98,12 +101,12 @@ public final class DefaultHttpDataSourceFactory extends BaseFactory {
* to HTTPS and vice versa) are enabled. * to HTTPS and vice versa) are enabled.
*/ */
public DefaultHttpDataSourceFactory( public DefaultHttpDataSourceFactory(
String userAgent, @Nullable String userAgent,
@Nullable TransferListener listener, @Nullable TransferListener listener,
int connectTimeoutMillis, int connectTimeoutMillis,
int readTimeoutMillis, int readTimeoutMillis,
boolean allowCrossProtocolRedirects) { boolean allowCrossProtocolRedirects) {
this.userAgent = Assertions.checkNotEmpty(userAgent); this.userAgent = userAgent;
this.listener = listener; this.listener = listener;
this.connectTimeoutMillis = connectTimeoutMillis; this.connectTimeoutMillis = connectTimeoutMillis;
this.readTimeoutMillis = readTimeoutMillis; this.readTimeoutMillis = readTimeoutMillis;