diff --git a/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java b/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java index 1a63a2a3cb..e095465043 100644 --- a/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java +++ b/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java @@ -33,7 +33,6 @@ import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Util; import com.google.common.base.Predicate; import com.google.common.net.HttpHeaders; -import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; @@ -478,10 +477,6 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource { int read = castNonNull(responseByteStream).read(buffer, offset, readLength); if (read == -1) { - if (bytesToRead != C.LENGTH_UNSET) { - // End of stream reached having not read sufficient data. - throw new EOFException(); - } return C.RESULT_END_OF_INPUT; } diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java b/library/common/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java index 9376db604f..15a79c5677 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java @@ -29,7 +29,6 @@ import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Util; import com.google.common.base.Predicate; import com.google.common.net.HttpHeaders; -import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; @@ -692,10 +691,6 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou int read = castNonNull(inputStream).read(buffer, offset, readLength); if (read == -1) { - if (bytesToRead != C.LENGTH_UNSET) { - // End of stream reached having not read sufficient data. - throw new EOFException(); - } return C.RESULT_END_OF_INPUT; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/ProgressiveDownloader.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/ProgressiveDownloader.java index a4cbe17b82..35a3e788f5 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/ProgressiveDownloader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/ProgressiveDownloader.java @@ -108,7 +108,6 @@ public final class ProgressiveDownloader implements Downloader { new CacheWriter( dataSource, dataSpec, - /* allowShortContent= */ false, /* temporaryBuffer= */ null, progressListener); priorityTaskManager = cacheDataSourceFactory.getUpstreamPriorityTaskManager(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/SegmentDownloader.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/SegmentDownloader.java index 7cf31bc030..74df93d82f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/SegmentDownloader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/SegmentDownloader.java @@ -472,7 +472,6 @@ public abstract class SegmentDownloader> impleme new CacheWriter( dataSource, segment.dataSpec, - /* allowShortContent= */ false, temporaryBuffer, progressNotifier); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/AssetDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/AssetDataSource.java index a7ec8fd81e..05838c8a2d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/AssetDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/AssetDataSource.java @@ -24,7 +24,6 @@ import android.net.Uri; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.util.Assertions; -import java.io.EOFException; import java.io.IOException; import java.io.InputStream; @@ -111,10 +110,6 @@ public final class AssetDataSource extends BaseDataSource { } if (bytesRead == -1) { - if (bytesRemaining != C.LENGTH_UNSET) { - // End of stream reached having not read sufficient data. - throw new AssetDataSourceException(new EOFException()); - } return C.RESULT_END_OF_INPUT; } if (bytesRemaining != C.LENGTH_UNSET) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/ContentDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/ContentDataSource.java index 0713cfabc7..be93f883fe 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/ContentDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/ContentDataSource.java @@ -24,7 +24,6 @@ import android.content.res.AssetFileDescriptor; import android.net.Uri; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; -import java.io.EOFException; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; @@ -147,10 +146,6 @@ public final class ContentDataSource extends BaseDataSource { } if (bytesRead == -1) { - if (bytesRemaining != C.LENGTH_UNSET) { - // End of stream reached having not read sufficient data. - throw new ContentDataSourceException(new EOFException()); - } return C.RESULT_END_OF_INPUT; } if (bytesRemaining != C.LENGTH_UNSET) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/ExperimentalBandwidthMeter.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/ExperimentalBandwidthMeter.java new file mode 100644 index 0000000000..5015b361e6 --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/ExperimentalBandwidthMeter.java @@ -0,0 +1,720 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.upstream; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.ConnectivityManager; +import android.os.Handler; +import android.os.Looper; +import androidx.annotation.Nullable; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.upstream.BandwidthMeter.EventListener.EventDispatcher; +import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.util.Clock; +import com.google.android.exoplayer2.util.Util; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableListMultimap; +import com.google.common.collect.ImmutableMap; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; + +/** + * An experimental {@link BandwidthMeter} that estimates bandwidth by listening to data transfers. + * + *

The initial estimate is based on the current operator's network country code or the locale of + * the user, as well as the network connection type. This can be configured in the {@link Builder}. + */ +public final class ExperimentalBandwidthMeter implements BandwidthMeter, TransferListener { + + /** + * Country groups used to determine the default initial bitrate estimate. The group assignment for + * each country is a list for [Wifi, 2G, 3G, 4G, 5G_NSA]. + */ + public static final ImmutableListMultimap + DEFAULT_INITIAL_BITRATE_COUNTRY_GROUPS = createInitialBitrateCountryGroupAssignment(); + + /** Default initial Wifi bitrate estimate in bits per second. */ + public static final ImmutableList DEFAULT_INITIAL_BITRATE_ESTIMATES_WIFI = + ImmutableList.of(6_100_000L, 3_900_000L, 2_300_000L, 1_300_000L, 600_000L); + + /** Default initial 2G bitrate estimates in bits per second. */ + public static final ImmutableList DEFAULT_INITIAL_BITRATE_ESTIMATES_2G = + ImmutableList.of(230_000L, 159_000L, 142_000L, 127_000L, 112_000L); + + /** Default initial 3G bitrate estimates in bits per second. */ + public static final ImmutableList DEFAULT_INITIAL_BITRATE_ESTIMATES_3G = + ImmutableList.of(2_200_000L, 1_300_000L, 940_000L, 760_000L, 520_000L); + + /** Default initial 4G bitrate estimates in bits per second. */ + public static final ImmutableList DEFAULT_INITIAL_BITRATE_ESTIMATES_4G = + ImmutableList.of(4_400_000L, 2_300_000L, 1_500_000L, 1_100_000L, 660_000L); + + /** Default initial 5G-NSA bitrate estimates in bits per second. */ + public static final ImmutableList DEFAULT_INITIAL_BITRATE_ESTIMATES_5G_NSA = + ImmutableList.of(13_000_000L, 9_100_000L, 6_300_000L, 4_000_000L, 2_000_000L); + + @Nullable private static ExperimentalBandwidthMeter singletonInstance; + + /** + * Default initial bitrate estimate used when the device is offline or the network type cannot be + * determined, in bits per second. + */ + public static final long DEFAULT_INITIAL_BITRATE_ESTIMATE = 1_000_000; + + /** Index for the Wifi group index in {@link #DEFAULT_INITIAL_BITRATE_COUNTRY_GROUPS}. */ + private static final int COUNTRY_GROUP_INDEX_WIFI = 0; + /** Index for the 2G group index in {@link #DEFAULT_INITIAL_BITRATE_COUNTRY_GROUPS}. */ + private static final int COUNTRY_GROUP_INDEX_2G = 1; + /** Index for the 3G group index in {@link #DEFAULT_INITIAL_BITRATE_COUNTRY_GROUPS}. */ + private static final int COUNTRY_GROUP_INDEX_3G = 2; + /** Index for the 4G group index in {@link #DEFAULT_INITIAL_BITRATE_COUNTRY_GROUPS}. */ + private static final int COUNTRY_GROUP_INDEX_4G = 3; + /** Index for the 5G-NSA group index in {@link #DEFAULT_INITIAL_BITRATE_COUNTRY_GROUPS}. */ + private static final int COUNTRY_GROUP_INDEX_5G_NSA = 4; + + /** Builder for a bandwidth meter. */ + public static final class Builder { + + @Nullable private final Context context; + + private Map initialBitrateEstimates; + private Clock clock; + private boolean resetOnNetworkTypeChange; + + /** + * Creates a builder with default parameters and without listener. + * + * @param context A context. + */ + public Builder(Context context) { + // Handling of null is for backward compatibility only. + this.context = context == null ? null : context.getApplicationContext(); + initialBitrateEstimates = getInitialBitrateEstimatesForCountry(Util.getCountryCode(context)); + clock = Clock.DEFAULT; + resetOnNetworkTypeChange = true; + } + + /** + * Sets the initial bitrate estimate in bits per second that should be assumed when a bandwidth + * estimate is unavailable. + * + * @param initialBitrateEstimate The initial bitrate estimate in bits per second. + * @return This builder. + */ + public Builder setInitialBitrateEstimate(long initialBitrateEstimate) { + for (Integer networkType : initialBitrateEstimates.keySet()) { + setInitialBitrateEstimate(networkType, initialBitrateEstimate); + } + return this; + } + + /** + * Sets the initial bitrate estimate in bits per second that should be assumed when a bandwidth + * estimate is unavailable and the current network connection is of the specified type. + * + * @param networkType The {@link C.NetworkType} this initial estimate is for. + * @param initialBitrateEstimate The initial bitrate estimate in bits per second. + * @return This builder. + */ + public Builder setInitialBitrateEstimate( + @C.NetworkType int networkType, long initialBitrateEstimate) { + initialBitrateEstimates.put(networkType, initialBitrateEstimate); + return this; + } + + /** + * Sets the initial bitrate estimates to the default values of the specified country. The + * initial estimates are used when a bandwidth estimate is unavailable. + * + * @param countryCode The ISO 3166-1 alpha-2 country code of the country whose default bitrate + * estimates should be used. + * @return This builder. + */ + public Builder setInitialBitrateEstimate(String countryCode) { + initialBitrateEstimates = + getInitialBitrateEstimatesForCountry(Util.toUpperInvariant(countryCode)); + return this; + } + + /** + * Sets the clock used to estimate bandwidth from data transfers. Should only be set for testing + * purposes. + * + * @param clock The clock used to estimate bandwidth from data transfers. + * @return This builder. + */ + public Builder setClock(Clock clock) { + this.clock = clock; + return this; + } + + /** + * Sets whether to reset if the network type changes. The default value is {@code true}. + * + * @param resetOnNetworkTypeChange Whether to reset if the network type changes. + * @return This builder. + */ + public Builder setResetOnNetworkTypeChange(boolean resetOnNetworkTypeChange) { + this.resetOnNetworkTypeChange = resetOnNetworkTypeChange; + return this; + } + + /** + * Builds the bandwidth meter. + * + * @return A bandwidth meter with the configured properties. + */ + public ExperimentalBandwidthMeter build() { + return new ExperimentalBandwidthMeter( + context, initialBitrateEstimates, clock, resetOnNetworkTypeChange); + } + + private static Map getInitialBitrateEstimatesForCountry(String countryCode) { + List groupIndices = getCountryGroupIndices(countryCode); + Map result = new HashMap<>(/* initialCapacity= */ 6); + result.put(C.NETWORK_TYPE_UNKNOWN, DEFAULT_INITIAL_BITRATE_ESTIMATE); + result.put( + C.NETWORK_TYPE_WIFI, + DEFAULT_INITIAL_BITRATE_ESTIMATES_WIFI.get(groupIndices.get(COUNTRY_GROUP_INDEX_WIFI))); + result.put( + C.NETWORK_TYPE_2G, + DEFAULT_INITIAL_BITRATE_ESTIMATES_2G.get(groupIndices.get(COUNTRY_GROUP_INDEX_2G))); + result.put( + C.NETWORK_TYPE_3G, + DEFAULT_INITIAL_BITRATE_ESTIMATES_3G.get(groupIndices.get(COUNTRY_GROUP_INDEX_3G))); + result.put( + C.NETWORK_TYPE_4G, + DEFAULT_INITIAL_BITRATE_ESTIMATES_4G.get(groupIndices.get(COUNTRY_GROUP_INDEX_4G))); + result.put( + C.NETWORK_TYPE_5G, + DEFAULT_INITIAL_BITRATE_ESTIMATES_5G_NSA.get( + groupIndices.get(COUNTRY_GROUP_INDEX_5G_NSA))); + // Assume default Wifi speed for Ethernet to prevent using the slower fallback. + result.put( + C.NETWORK_TYPE_ETHERNET, + DEFAULT_INITIAL_BITRATE_ESTIMATES_WIFI.get(groupIndices.get(COUNTRY_GROUP_INDEX_WIFI))); + return result; + } + + private static ImmutableList getCountryGroupIndices(String countryCode) { + ImmutableList groupIndices = DEFAULT_INITIAL_BITRATE_COUNTRY_GROUPS.get(countryCode); + // Assume median group if not found. + return groupIndices.isEmpty() ? ImmutableList.of(2, 2, 2, 2, 2) : groupIndices; + } + } + + /** + * Returns a singleton instance of an {@link ExperimentalBandwidthMeter} with default + * configuration. + * + * @param context A {@link Context}. + * @return The singleton instance. + */ + public static synchronized ExperimentalBandwidthMeter getSingletonInstance(Context context) { + if (singletonInstance == null) { + singletonInstance = new Builder(context).build(); + } + return singletonInstance; + } + + @Nullable private final Context context; + private final ImmutableMap initialBitrateEstimates; + private final EventDispatcher eventDispatcher; + private final Clock clock; + + private int streamCount; + private long sampleStartTimeMs; + private long sampleBytesTransferred; + + @C.NetworkType private int networkType; + private long bitrateEstimate; + private long lastReportedBitrateEstimate; + + private boolean networkTypeOverrideSet; + @C.NetworkType private int networkTypeOverride; + + private ExperimentalBandwidthMeter( + @Nullable Context context, + Map initialBitrateEstimates, + Clock clock, + boolean resetOnNetworkTypeChange) { + this.context = context == null ? null : context.getApplicationContext(); + this.initialBitrateEstimates = ImmutableMap.copyOf(initialBitrateEstimates); + this.eventDispatcher = new EventDispatcher(); + this.clock = clock; + // Set the initial network type and bitrate estimate + networkType = context == null ? C.NETWORK_TYPE_UNKNOWN : Util.getNetworkType(context); + bitrateEstimate = getInitialBitrateEstimateForNetworkType(networkType); + // Register to receive connectivity actions if possible. + if (context != null && resetOnNetworkTypeChange) { + ConnectivityActionReceiver connectivityActionReceiver = + ConnectivityActionReceiver.getInstance(context); + connectivityActionReceiver.register(/* bandwidthMeter= */ this); + } + } + + /** + * Overrides the network type. Handled in the same way as if the meter had detected a change from + * the current network type to the specified network type internally. + * + *

Applications should not normally call this method. It is intended for testing purposes. + * + * @param networkType The overriding network type. + */ + public synchronized void setNetworkTypeOverride(@C.NetworkType int networkType) { + networkTypeOverride = networkType; + networkTypeOverrideSet = true; + onConnectivityAction(); + } + + @Override + public synchronized long getBitrateEstimate() { + return bitrateEstimate; + } + + @Override + public TransferListener getTransferListener() { + return this; + } + + @Override + public void addEventListener(Handler eventHandler, EventListener eventListener) { + Assertions.checkNotNull(eventHandler); + Assertions.checkNotNull(eventListener); + eventDispatcher.addListener(eventHandler, eventListener); + } + + @Override + public void removeEventListener(EventListener eventListener) { + eventDispatcher.removeListener(eventListener); + } + + @Override + public void onTransferInitializing(DataSource source, DataSpec dataSpec, boolean isNetwork) { + // TODO: Track time for time-to-response estimation. + } + + @Override + public synchronized void onTransferStart( + DataSource source, DataSpec dataSpec, boolean isNetwork) { + if (!isTransferAtFullNetworkSpeed(dataSpec, isNetwork)) { + return; + } + if (streamCount == 0) { + sampleStartTimeMs = clock.elapsedRealtime(); + } + streamCount++; + + // TODO: Track time for time-to-response estimation. + } + + @Override + public synchronized void onBytesTransferred( + DataSource source, DataSpec dataSpec, boolean isNetwork, int bytes) { + if (!isTransferAtFullNetworkSpeed(dataSpec, isNetwork)) { + return; + } + sampleBytesTransferred += bytes; + } + + @Override + public synchronized void onTransferEnd(DataSource source, DataSpec dataSpec, boolean isNetwork) { + if (!isTransferAtFullNetworkSpeed(dataSpec, isNetwork)) { + return; + } + Assertions.checkState(streamCount > 0); + long nowMs = clock.elapsedRealtime(); + int sampleElapsedTimeMs = (int) (nowMs - sampleStartTimeMs); + if (sampleElapsedTimeMs > 0) { + // TODO: Add heuristic for bandwidth estimation. + bitrateEstimate = (long) (sampleBytesTransferred * 8000f) / sampleElapsedTimeMs; + maybeNotifyBandwidthSample(sampleElapsedTimeMs, sampleBytesTransferred, bitrateEstimate); + sampleStartTimeMs = nowMs; + sampleBytesTransferred = 0; + } // Else any sample bytes transferred will be carried forward into the next sample. + streamCount--; + } + + private synchronized void onConnectivityAction() { + int networkType = + networkTypeOverrideSet + ? networkTypeOverride + : (context == null ? C.NETWORK_TYPE_UNKNOWN : Util.getNetworkType(context)); + if (this.networkType == networkType) { + return; + } + + this.networkType = networkType; + if (networkType == C.NETWORK_TYPE_OFFLINE + || networkType == C.NETWORK_TYPE_UNKNOWN + || networkType == C.NETWORK_TYPE_OTHER) { + // It's better not to reset the bandwidth meter for these network types. + return; + } + + // Reset the bitrate estimate and report it, along with any bytes transferred. + this.bitrateEstimate = getInitialBitrateEstimateForNetworkType(networkType); + long nowMs = clock.elapsedRealtime(); + int sampleElapsedTimeMs = streamCount > 0 ? (int) (nowMs - sampleStartTimeMs) : 0; + maybeNotifyBandwidthSample(sampleElapsedTimeMs, sampleBytesTransferred, bitrateEstimate); + + // Reset the remainder of the state. + sampleStartTimeMs = nowMs; + sampleBytesTransferred = 0; + } + + private void maybeNotifyBandwidthSample( + int elapsedMs, long bytesTransferred, long bitrateEstimate) { + if (elapsedMs == 0 && bytesTransferred == 0 && bitrateEstimate == lastReportedBitrateEstimate) { + return; + } + lastReportedBitrateEstimate = bitrateEstimate; + eventDispatcher.bandwidthSample(elapsedMs, bytesTransferred, bitrateEstimate); + } + + private long getInitialBitrateEstimateForNetworkType(@C.NetworkType int networkType) { + Long initialBitrateEstimate = initialBitrateEstimates.get(networkType); + if (initialBitrateEstimate == null) { + initialBitrateEstimate = initialBitrateEstimates.get(C.NETWORK_TYPE_UNKNOWN); + } + if (initialBitrateEstimate == null) { + initialBitrateEstimate = DEFAULT_INITIAL_BITRATE_ESTIMATE; + } + return initialBitrateEstimate; + } + + private static boolean isTransferAtFullNetworkSpeed(DataSpec dataSpec, boolean isNetwork) { + return isNetwork && !dataSpec.isFlagSet(DataSpec.FLAG_MIGHT_NOT_USE_FULL_NETWORK_SPEED); + } + + /* + * Note: This class only holds a weak reference to bandwidth meter instances. It should not + * be made non-static, since doing so adds a strong reference (i.e., + * ExperimentalBandwidthMeter.this). + */ + private static class ConnectivityActionReceiver extends BroadcastReceiver { + + private static @MonotonicNonNull ConnectivityActionReceiver staticInstance; + + private final Handler mainHandler; + private final ArrayList> bandwidthMeters; + + public static synchronized ConnectivityActionReceiver getInstance(Context context) { + if (staticInstance == null) { + staticInstance = new ConnectivityActionReceiver(); + IntentFilter filter = new IntentFilter(); + filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); + context.registerReceiver(staticInstance, filter); + } + return staticInstance; + } + + private ConnectivityActionReceiver() { + mainHandler = new Handler(Looper.getMainLooper()); + bandwidthMeters = new ArrayList<>(); + } + + public synchronized void register(ExperimentalBandwidthMeter bandwidthMeter) { + removeClearedReferences(); + bandwidthMeters.add(new WeakReference<>(bandwidthMeter)); + // Simulate an initial update on the main thread (like the sticky broadcast we'd receive if + // we were to register a separate broadcast receiver for each bandwidth meter). + mainHandler.post(() -> updateBandwidthMeter(bandwidthMeter)); + } + + @Override + public synchronized void onReceive(Context context, Intent intent) { + if (isInitialStickyBroadcast()) { + return; + } + removeClearedReferences(); + for (int i = 0; i < bandwidthMeters.size(); i++) { + WeakReference bandwidthMeterReference = bandwidthMeters.get(i); + ExperimentalBandwidthMeter bandwidthMeter = bandwidthMeterReference.get(); + if (bandwidthMeter != null) { + updateBandwidthMeter(bandwidthMeter); + } + } + } + + private void updateBandwidthMeter(ExperimentalBandwidthMeter bandwidthMeter) { + bandwidthMeter.onConnectivityAction(); + } + + private void removeClearedReferences() { + for (int i = bandwidthMeters.size() - 1; i >= 0; i--) { + WeakReference bandwidthMeterReference = bandwidthMeters.get(i); + ExperimentalBandwidthMeter bandwidthMeter = bandwidthMeterReference.get(); + if (bandwidthMeter == null) { + bandwidthMeters.remove(i); + } + } + } + } + + private static ImmutableListMultimap + createInitialBitrateCountryGroupAssignment() { + ImmutableListMultimap.Builder countryGroupAssignment = + ImmutableListMultimap.builder(); + countryGroupAssignment.putAll("AD", 1, 2, 0, 0, 2); + countryGroupAssignment.putAll("AE", 1, 4, 4, 4, 2); + countryGroupAssignment.putAll("AF", 4, 4, 4, 4, 2); + countryGroupAssignment.putAll("AG", 4, 2, 1, 4, 2); + countryGroupAssignment.putAll("AI", 1, 2, 2, 2, 2); + countryGroupAssignment.putAll("AL", 1, 1, 1, 1, 2); + countryGroupAssignment.putAll("AM", 2, 2, 1, 3, 2); + countryGroupAssignment.putAll("AO", 3, 4, 3, 1, 2); + countryGroupAssignment.putAll("AR", 2, 4, 2, 1, 2); + countryGroupAssignment.putAll("AS", 2, 2, 3, 3, 2); + countryGroupAssignment.putAll("AT", 0, 2, 0, 0, 0); + countryGroupAssignment.putAll("AU", 0, 2, 0, 1, 1); + countryGroupAssignment.putAll("AW", 1, 2, 0, 4, 2); + countryGroupAssignment.putAll("AX", 0, 2, 2, 2, 2); + countryGroupAssignment.putAll("AZ", 3, 3, 3, 4, 2); + countryGroupAssignment.putAll("BA", 1, 1, 0, 1, 2); + countryGroupAssignment.putAll("BB", 0, 2, 0, 0, 2); + countryGroupAssignment.putAll("BD", 2, 0, 3, 3, 2); + countryGroupAssignment.putAll("BE", 0, 0, 2, 3, 2); + countryGroupAssignment.putAll("BF", 4, 4, 4, 2, 2); + countryGroupAssignment.putAll("BG", 0, 1, 0, 0, 2); + countryGroupAssignment.putAll("BH", 1, 0, 2, 4, 3); + countryGroupAssignment.putAll("BI", 4, 4, 4, 4, 2); + countryGroupAssignment.putAll("BJ", 4, 4, 4, 4, 2); + countryGroupAssignment.putAll("BL", 1, 2, 2, 2, 2); + countryGroupAssignment.putAll("BM", 0, 2, 0, 0, 2); + countryGroupAssignment.putAll("BN", 3, 2, 1, 0, 2); + countryGroupAssignment.putAll("BO", 1, 2, 4, 2, 2); + countryGroupAssignment.putAll("BQ", 1, 2, 1, 3, 2); + countryGroupAssignment.putAll("BR", 2, 4, 2, 2, 3); + countryGroupAssignment.putAll("BS", 2, 2, 1, 3, 2); + countryGroupAssignment.putAll("BT", 3, 0, 3, 2, 2); + countryGroupAssignment.putAll("BW", 3, 4, 1, 1, 2); + countryGroupAssignment.putAll("BY", 1, 1, 1, 2, 2); + countryGroupAssignment.putAll("BZ", 2, 2, 2, 2, 2); + countryGroupAssignment.putAll("CA", 0, 3, 1, 2, 4); + countryGroupAssignment.putAll("CD", 4, 3, 2, 1, 2); + countryGroupAssignment.putAll("CF", 4, 2, 3, 2, 2); + countryGroupAssignment.putAll("CG", 3, 4, 2, 2, 2); + countryGroupAssignment.putAll("CH", 0, 0, 0, 0, 2); + countryGroupAssignment.putAll("CI", 3, 3, 3, 3, 2); + countryGroupAssignment.putAll("CK", 2, 2, 3, 0, 2); + countryGroupAssignment.putAll("CL", 1, 1, 2, 2, 2); + countryGroupAssignment.putAll("CM", 3, 4, 3, 3, 2); + countryGroupAssignment.putAll("CN", 2, 2, 2, 1, 4); + countryGroupAssignment.putAll("CO", 2, 3, 4, 2, 2); + countryGroupAssignment.putAll("CR", 2, 3, 4, 4, 2); + countryGroupAssignment.putAll("CU", 4, 4, 2, 2, 2); + countryGroupAssignment.putAll("CV", 2, 3, 1, 0, 2); + countryGroupAssignment.putAll("CW", 1, 2, 0, 0, 2); + countryGroupAssignment.putAll("CY", 1, 1, 0, 0, 2); + countryGroupAssignment.putAll("CZ", 0, 1, 0, 0, 1); + countryGroupAssignment.putAll("DE", 0, 0, 1, 1, 0); + countryGroupAssignment.putAll("DJ", 4, 0, 4, 4, 2); + countryGroupAssignment.putAll("DK", 0, 0, 1, 0, 0); + countryGroupAssignment.putAll("DM", 1, 2, 2, 2, 2); + countryGroupAssignment.putAll("DO", 3, 4, 4, 4, 2); + countryGroupAssignment.putAll("DZ", 3, 3, 4, 4, 2); + countryGroupAssignment.putAll("EC", 2, 4, 3, 2, 2); + countryGroupAssignment.putAll("EE", 0, 1, 0, 0, 2); + countryGroupAssignment.putAll("EG", 3, 4, 3, 3, 2); + countryGroupAssignment.putAll("EH", 2, 2, 2, 2, 2); + countryGroupAssignment.putAll("ER", 4, 2, 2, 2, 2); + countryGroupAssignment.putAll("ES", 0, 1, 1, 1, 2); + countryGroupAssignment.putAll("ET", 4, 4, 4, 1, 2); + countryGroupAssignment.putAll("FI", 0, 0, 0, 0, 0); + countryGroupAssignment.putAll("FJ", 3, 0, 2, 2, 2); + countryGroupAssignment.putAll("FK", 4, 2, 2, 2, 2); + countryGroupAssignment.putAll("FM", 3, 2, 4, 4, 2); + countryGroupAssignment.putAll("FO", 1, 2, 0, 1, 2); + countryGroupAssignment.putAll("FR", 1, 1, 2, 0, 1); + countryGroupAssignment.putAll("GA", 3, 4, 1, 1, 2); + countryGroupAssignment.putAll("GB", 0, 0, 1, 1, 1); + countryGroupAssignment.putAll("GD", 1, 2, 2, 2, 2); + countryGroupAssignment.putAll("GE", 1, 1, 1, 3, 2); + countryGroupAssignment.putAll("GF", 2, 2, 2, 3, 2); + countryGroupAssignment.putAll("GG", 1, 2, 0, 0, 2); + countryGroupAssignment.putAll("GH", 3, 1, 3, 2, 2); + countryGroupAssignment.putAll("GI", 0, 2, 2, 0, 2); + countryGroupAssignment.putAll("GL", 1, 2, 0, 0, 2); + countryGroupAssignment.putAll("GM", 4, 3, 2, 4, 2); + countryGroupAssignment.putAll("GN", 3, 3, 4, 2, 2); + countryGroupAssignment.putAll("GP", 2, 1, 2, 3, 2); + countryGroupAssignment.putAll("GQ", 4, 2, 2, 4, 2); + countryGroupAssignment.putAll("GR", 1, 2, 0, 1, 2); + countryGroupAssignment.putAll("GT", 3, 2, 2, 1, 2); + countryGroupAssignment.putAll("GU", 1, 2, 3, 4, 2); + countryGroupAssignment.putAll("GW", 4, 4, 4, 4, 2); + countryGroupAssignment.putAll("GY", 3, 3, 3, 4, 2); + countryGroupAssignment.putAll("HK", 0, 1, 2, 3, 2); + countryGroupAssignment.putAll("HN", 3, 1, 3, 3, 2); + countryGroupAssignment.putAll("HR", 1, 1, 0, 0, 3); + countryGroupAssignment.putAll("HT", 4, 4, 4, 4, 2); + countryGroupAssignment.putAll("HU", 0, 0, 0, 0, 1); + countryGroupAssignment.putAll("ID", 3, 2, 3, 3, 2); + countryGroupAssignment.putAll("IE", 0, 0, 1, 1, 3); + countryGroupAssignment.putAll("IL", 1, 0, 2, 3, 4); + countryGroupAssignment.putAll("IM", 0, 2, 0, 1, 2); + countryGroupAssignment.putAll("IN", 2, 1, 3, 3, 2); + countryGroupAssignment.putAll("IO", 4, 2, 2, 4, 2); + countryGroupAssignment.putAll("IQ", 3, 3, 4, 4, 2); + countryGroupAssignment.putAll("IR", 3, 2, 3, 2, 2); + countryGroupAssignment.putAll("IS", 0, 2, 0, 0, 2); + countryGroupAssignment.putAll("IT", 0, 4, 1, 1, 2); + countryGroupAssignment.putAll("JE", 2, 2, 1, 2, 2); + countryGroupAssignment.putAll("JM", 3, 3, 4, 4, 2); + countryGroupAssignment.putAll("JO", 2, 2, 1, 1, 2); + countryGroupAssignment.putAll("JP", 0, 0, 0, 0, 3); + countryGroupAssignment.putAll("KE", 3, 4, 2, 2, 2); + countryGroupAssignment.putAll("KG", 2, 0, 1, 1, 2); + countryGroupAssignment.putAll("KH", 1, 0, 4, 3, 2); + countryGroupAssignment.putAll("KI", 4, 2, 4, 3, 2); + countryGroupAssignment.putAll("KM", 4, 3, 3, 3, 2); + countryGroupAssignment.putAll("KN", 1, 2, 2, 2, 2); + countryGroupAssignment.putAll("KP", 4, 2, 2, 2, 2); + countryGroupAssignment.putAll("KR", 0, 0, 1, 3, 1); + countryGroupAssignment.putAll("KW", 1, 3, 0, 0, 0); + countryGroupAssignment.putAll("KY", 1, 2, 0, 2, 2); + countryGroupAssignment.putAll("KZ", 2, 2, 2, 3, 2); + countryGroupAssignment.putAll("LA", 2, 2, 1, 1, 2); + countryGroupAssignment.putAll("LB", 3, 2, 0, 0, 2); + countryGroupAssignment.putAll("LC", 1, 2, 0, 1, 2); + countryGroupAssignment.putAll("LI", 0, 2, 2, 2, 2); + countryGroupAssignment.putAll("LK", 2, 0, 2, 3, 2); + countryGroupAssignment.putAll("LR", 3, 4, 4, 3, 2); + countryGroupAssignment.putAll("LS", 3, 3, 2, 3, 2); + countryGroupAssignment.putAll("LT", 0, 0, 0, 0, 2); + countryGroupAssignment.putAll("LU", 1, 0, 1, 1, 2); + countryGroupAssignment.putAll("LV", 0, 0, 0, 0, 2); + countryGroupAssignment.putAll("LY", 4, 2, 4, 4, 2); + countryGroupAssignment.putAll("MA", 3, 2, 2, 1, 2); + countryGroupAssignment.putAll("MC", 0, 2, 0, 0, 2); + countryGroupAssignment.putAll("MD", 1, 2, 0, 0, 2); + countryGroupAssignment.putAll("ME", 1, 2, 0, 1, 2); + countryGroupAssignment.putAll("MF", 1, 2, 1, 1, 2); + countryGroupAssignment.putAll("MG", 3, 4, 2, 2, 2); + countryGroupAssignment.putAll("MH", 4, 2, 2, 4, 2); + countryGroupAssignment.putAll("MK", 1, 1, 0, 0, 2); + countryGroupAssignment.putAll("ML", 4, 4, 2, 2, 2); + countryGroupAssignment.putAll("MM", 2, 3, 3, 3, 2); + countryGroupAssignment.putAll("MN", 2, 4, 1, 2, 2); + countryGroupAssignment.putAll("MO", 0, 2, 4, 4, 2); + countryGroupAssignment.putAll("MP", 0, 2, 2, 2, 2); + countryGroupAssignment.putAll("MQ", 2, 2, 2, 3, 2); + countryGroupAssignment.putAll("MR", 3, 0, 4, 3, 2); + countryGroupAssignment.putAll("MS", 1, 2, 2, 2, 2); + countryGroupAssignment.putAll("MT", 0, 2, 0, 0, 2); + countryGroupAssignment.putAll("MU", 3, 1, 1, 2, 2); + countryGroupAssignment.putAll("MV", 4, 3, 3, 4, 2); + countryGroupAssignment.putAll("MW", 4, 2, 1, 0, 2); + countryGroupAssignment.putAll("MX", 2, 4, 3, 4, 2); + countryGroupAssignment.putAll("MY", 1, 0, 3, 2, 2); + countryGroupAssignment.putAll("MZ", 3, 3, 2, 1, 2); + countryGroupAssignment.putAll("NA", 4, 3, 3, 2, 2); + countryGroupAssignment.putAll("NC", 2, 0, 3, 4, 2); + countryGroupAssignment.putAll("NE", 4, 4, 4, 4, 2); + countryGroupAssignment.putAll("NF", 2, 2, 2, 2, 2); + countryGroupAssignment.putAll("NG", 3, 3, 2, 2, 2); + countryGroupAssignment.putAll("NI", 2, 1, 4, 4, 2); + countryGroupAssignment.putAll("NL", 0, 2, 3, 2, 0); + countryGroupAssignment.putAll("NO", 0, 1, 2, 0, 0); + countryGroupAssignment.putAll("NP", 2, 0, 4, 2, 2); + countryGroupAssignment.putAll("NR", 3, 2, 2, 1, 2); + countryGroupAssignment.putAll("NU", 4, 2, 2, 2, 2); + countryGroupAssignment.putAll("NZ", 0, 2, 1, 2, 4); + countryGroupAssignment.putAll("OM", 2, 2, 1, 3, 2); + countryGroupAssignment.putAll("PA", 1, 3, 3, 3, 2); + countryGroupAssignment.putAll("PE", 2, 3, 4, 4, 2); + countryGroupAssignment.putAll("PF", 2, 2, 2, 1, 2); + countryGroupAssignment.putAll("PG", 4, 4, 3, 2, 2); + countryGroupAssignment.putAll("PH", 2, 1, 3, 3, 4); + countryGroupAssignment.putAll("PK", 3, 2, 3, 3, 2); + countryGroupAssignment.putAll("PL", 1, 0, 1, 2, 3); + countryGroupAssignment.putAll("PM", 0, 2, 2, 2, 2); + countryGroupAssignment.putAll("PR", 2, 1, 2, 2, 4); + countryGroupAssignment.putAll("PS", 3, 3, 2, 2, 2); + countryGroupAssignment.putAll("PT", 0, 1, 1, 0, 2); + countryGroupAssignment.putAll("PW", 1, 2, 4, 0, 2); + countryGroupAssignment.putAll("PY", 2, 0, 3, 2, 2); + countryGroupAssignment.putAll("QA", 2, 3, 1, 2, 3); + countryGroupAssignment.putAll("RE", 1, 0, 2, 2, 2); + countryGroupAssignment.putAll("RO", 0, 1, 0, 1, 1); + countryGroupAssignment.putAll("RS", 1, 2, 0, 0, 2); + countryGroupAssignment.putAll("RU", 0, 1, 0, 1, 2); + countryGroupAssignment.putAll("RW", 3, 3, 4, 1, 2); + countryGroupAssignment.putAll("SA", 2, 2, 2, 1, 2); + countryGroupAssignment.putAll("SB", 4, 2, 3, 2, 2); + countryGroupAssignment.putAll("SC", 4, 1, 1, 3, 2); + countryGroupAssignment.putAll("SD", 4, 4, 4, 4, 2); + countryGroupAssignment.putAll("SE", 0, 0, 0, 0, 0); + countryGroupAssignment.putAll("SG", 1, 0, 1, 2, 3); + countryGroupAssignment.putAll("SH", 4, 2, 2, 2, 2); + countryGroupAssignment.putAll("SI", 0, 0, 0, 0, 2); + countryGroupAssignment.putAll("SJ", 2, 2, 2, 2, 2); + countryGroupAssignment.putAll("SK", 0, 1, 0, 0, 2); + countryGroupAssignment.putAll("SL", 4, 3, 4, 0, 2); + countryGroupAssignment.putAll("SM", 0, 2, 2, 2, 2); + countryGroupAssignment.putAll("SN", 4, 4, 4, 4, 2); + countryGroupAssignment.putAll("SO", 3, 3, 3, 4, 2); + countryGroupAssignment.putAll("SR", 3, 2, 2, 2, 2); + countryGroupAssignment.putAll("SS", 4, 4, 3, 3, 2); + countryGroupAssignment.putAll("ST", 2, 2, 1, 2, 2); + countryGroupAssignment.putAll("SV", 2, 1, 4, 3, 2); + countryGroupAssignment.putAll("SX", 2, 2, 1, 0, 2); + countryGroupAssignment.putAll("SY", 4, 3, 3, 2, 2); + countryGroupAssignment.putAll("SZ", 4, 3, 2, 4, 2); + countryGroupAssignment.putAll("TC", 2, 2, 2, 0, 2); + countryGroupAssignment.putAll("TD", 4, 3, 4, 4, 2); + countryGroupAssignment.putAll("TG", 3, 2, 2, 4, 2); + countryGroupAssignment.putAll("TH", 0, 3, 2, 3, 2); + countryGroupAssignment.putAll("TJ", 4, 4, 4, 4, 2); + countryGroupAssignment.putAll("TL", 4, 0, 4, 4, 2); + countryGroupAssignment.putAll("TM", 4, 2, 4, 3, 2); + countryGroupAssignment.putAll("TN", 2, 2, 1, 2, 2); + countryGroupAssignment.putAll("TO", 3, 2, 4, 3, 2); + countryGroupAssignment.putAll("TR", 1, 2, 0, 1, 2); + countryGroupAssignment.putAll("TT", 1, 4, 0, 1, 2); + countryGroupAssignment.putAll("TV", 3, 2, 2, 4, 2); + countryGroupAssignment.putAll("TW", 0, 0, 0, 0, 1); + countryGroupAssignment.putAll("TZ", 3, 3, 3, 2, 2); + countryGroupAssignment.putAll("UA", 0, 3, 1, 1, 2); + countryGroupAssignment.putAll("UG", 3, 2, 3, 3, 2); + countryGroupAssignment.putAll("US", 1, 1, 2, 2, 4); + countryGroupAssignment.putAll("UY", 2, 1, 1, 1, 2); + countryGroupAssignment.putAll("UZ", 2, 1, 3, 4, 2); + countryGroupAssignment.putAll("VC", 1, 2, 2, 2, 2); + countryGroupAssignment.putAll("VE", 4, 4, 4, 4, 2); + countryGroupAssignment.putAll("VG", 2, 2, 1, 1, 2); + countryGroupAssignment.putAll("VI", 1, 2, 1, 2, 2); + countryGroupAssignment.putAll("VN", 0, 1, 3, 4, 2); + countryGroupAssignment.putAll("VU", 4, 0, 3, 1, 2); + countryGroupAssignment.putAll("WF", 4, 2, 2, 2, 2); + countryGroupAssignment.putAll("WS", 3, 1, 3, 1, 2); + countryGroupAssignment.putAll("XK", 0, 1, 1, 1, 2); + countryGroupAssignment.putAll("YE", 4, 4, 4, 3, 2); + countryGroupAssignment.putAll("YT", 4, 2, 2, 3, 2); + countryGroupAssignment.putAll("ZA", 3, 3, 2, 1, 2); + countryGroupAssignment.putAll("ZM", 3, 2, 3, 3, 2); + countryGroupAssignment.putAll("ZW", 3, 2, 4, 3, 2); + return countryGroupAssignment.build(); + } +} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheWriter.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheWriter.java index 62a017df8e..a68f0764a3 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheWriter.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheWriter.java @@ -23,7 +23,6 @@ import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.util.PriorityTaskManager; import com.google.android.exoplayer2.util.PriorityTaskManager.PriorityTooLowException; import com.google.android.exoplayer2.util.Util; -import java.io.EOFException; import java.io.IOException; import java.io.InterruptedIOException; @@ -51,7 +50,6 @@ public final class CacheWriter { private final CacheDataSource dataSource; private final Cache cache; private final DataSpec dataSpec; - private final boolean allowShortContent; private final String cacheKey; private final byte[] temporaryBuffer; @Nullable private final ProgressListener progressListener; @@ -65,10 +63,6 @@ public final class CacheWriter { /** * @param dataSource A {@link CacheDataSource} that writes to the target cache. * @param dataSpec Defines the data to be written. - * @param allowShortContent Whether it's allowed for the content to end before the request as - * defined by the {@link DataSpec}. If {@code true} and the request exceeds the length of the - * content, then the content will be cached to the end. If {@code false} and the request - * exceeds the length of the content, {@link #cache} will throw an {@link IOException}. * @param temporaryBuffer A temporary buffer to be used during caching, or {@code null} if the * writer should instantiate its own internal temporary buffer. * @param progressListener An optional progress listener. @@ -76,13 +70,11 @@ public final class CacheWriter { public CacheWriter( CacheDataSource dataSource, DataSpec dataSpec, - boolean allowShortContent, @Nullable byte[] temporaryBuffer, @Nullable ProgressListener progressListener) { this.dataSource = dataSource; this.cache = dataSource.getCache(); this.dataSpec = dataSpec; - this.allowShortContent = allowShortContent; this.temporaryBuffer = temporaryBuffer == null ? new byte[DEFAULT_BUFFER_SIZE_BYTES] : temporaryBuffer; this.progressListener = progressListener; @@ -143,14 +135,6 @@ public final class CacheWriter { nextPosition += readBlockToCache(nextPosition, nextRequestLength); } } - - // TODO: Remove allowShortContent parameter, this code block, and return the number of bytes - // cached. The caller can then check whether fewer bytes were cached than were requested. - if (!allowShortContent - && dataSpec.length != C.LENGTH_UNSET - && nextPosition != dataSpec.position + dataSpec.length) { - throw new EOFException(); - } } /** @@ -176,9 +160,11 @@ public final class CacheWriter { isDataSourceOpen = true; } catch (IOException e) { Util.closeQuietly(dataSource); - if (allowShortContent - && isLastBlock - && DataSourceException.isCausedByPositionOutOfRange(e)) { + // TODO: This exception handling may be required for interop with current HttpDataSource + // implementations that (incorrectly) throw a position-out-of-range when opened exactly one + // byte beyond the end of the resource. It should be removed when the HttpDataSource + // implementations are fixed. + if (isLastBlock && DataSourceException.isCausedByPositionOutOfRange(e)) { // The length of the request exceeds the length of the content. If we allow shorter // content and are reading the last block, fall through and try again with an unbounded // request to read up to the end of the content. diff --git a/library/core/src/test/java/com/google/android/exoplayer2/upstream/ExperimentalBandwidthMeterTest.java b/library/core/src/test/java/com/google/android/exoplayer2/upstream/ExperimentalBandwidthMeterTest.java new file mode 100644 index 0000000000..cb4675badb --- /dev/null +++ b/library/core/src/test/java/com/google/android/exoplayer2/upstream/ExperimentalBandwidthMeterTest.java @@ -0,0 +1,583 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.upstream; + +import static android.net.NetworkInfo.State.CONNECTED; +import static android.net.NetworkInfo.State.DISCONNECTED; +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.net.NetworkInfo.DetailedState; +import android.net.Uri; +import android.telephony.TelephonyManager; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.testutil.FakeClock; +import com.google.android.exoplayer2.testutil.FakeDataSource; +import java.util.Random; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.Shadows; +import org.robolectric.shadows.ShadowNetworkInfo; + +/** Unit test for {@link ExperimentalBandwidthMeter}. */ +@RunWith(AndroidJUnit4.class) +public final class ExperimentalBandwidthMeterTest { + + private static final int SIMULATED_TRANSFER_COUNT = 100; + private static final String FAST_COUNTRY_ISO = "EE"; + private static final String SLOW_COUNTRY_ISO = "PG"; + + private TelephonyManager telephonyManager; + private ConnectivityManager connectivityManager; + private NetworkInfo networkInfoOffline; + private NetworkInfo networkInfoWifi; + private NetworkInfo networkInfo2g; + private NetworkInfo networkInfo3g; + private NetworkInfo networkInfo4g; + private NetworkInfo networkInfoEthernet; + + @Before + public void setUp() { + connectivityManager = + (ConnectivityManager) + ApplicationProvider.getApplicationContext() + .getSystemService(Context.CONNECTIVITY_SERVICE); + telephonyManager = + (TelephonyManager) + ApplicationProvider.getApplicationContext().getSystemService(Context.TELEPHONY_SERVICE); + Shadows.shadowOf(telephonyManager).setNetworkCountryIso(FAST_COUNTRY_ISO); + networkInfoOffline = + ShadowNetworkInfo.newInstance( + DetailedState.DISCONNECTED, + ConnectivityManager.TYPE_WIFI, + /* subType= */ 0, + /* isAvailable= */ false, + DISCONNECTED); + networkInfoWifi = + ShadowNetworkInfo.newInstance( + DetailedState.CONNECTED, + ConnectivityManager.TYPE_WIFI, + /* subType= */ 0, + /* isAvailable= */ true, + CONNECTED); + networkInfo2g = + ShadowNetworkInfo.newInstance( + DetailedState.CONNECTED, + ConnectivityManager.TYPE_MOBILE, + TelephonyManager.NETWORK_TYPE_GPRS, + /* isAvailable= */ true, + CONNECTED); + networkInfo3g = + ShadowNetworkInfo.newInstance( + DetailedState.CONNECTED, + ConnectivityManager.TYPE_MOBILE, + TelephonyManager.NETWORK_TYPE_HSDPA, + /* isAvailable= */ true, + CONNECTED); + networkInfo4g = + ShadowNetworkInfo.newInstance( + DetailedState.CONNECTED, + ConnectivityManager.TYPE_MOBILE, + TelephonyManager.NETWORK_TYPE_LTE, + /* isAvailable= */ true, + CONNECTED); + networkInfoEthernet = + ShadowNetworkInfo.newInstance( + DetailedState.CONNECTED, + ConnectivityManager.TYPE_ETHERNET, + /* subType= */ 0, + /* isAvailable= */ true, + CONNECTED); + } + + @Test + public void defaultInitialBitrateEstimate_forWifi_isGreaterThanEstimateFor2G() { + setActiveNetworkInfo(networkInfoWifi); + ExperimentalBandwidthMeter bandwidthMeterWifi = + new ExperimentalBandwidthMeter.Builder(ApplicationProvider.getApplicationContext()).build(); + long initialEstimateWifi = bandwidthMeterWifi.getBitrateEstimate(); + + setActiveNetworkInfo(networkInfo2g); + ExperimentalBandwidthMeter bandwidthMeter2g = + new ExperimentalBandwidthMeter.Builder(ApplicationProvider.getApplicationContext()).build(); + long initialEstimate2g = bandwidthMeter2g.getBitrateEstimate(); + + assertThat(initialEstimateWifi).isGreaterThan(initialEstimate2g); + } + + @Test + public void defaultInitialBitrateEstimate_forWifi_isGreaterThanEstimateFor3G() { + setActiveNetworkInfo(networkInfoWifi); + ExperimentalBandwidthMeter bandwidthMeterWifi = + new ExperimentalBandwidthMeter.Builder(ApplicationProvider.getApplicationContext()).build(); + long initialEstimateWifi = bandwidthMeterWifi.getBitrateEstimate(); + + setActiveNetworkInfo(networkInfo3g); + ExperimentalBandwidthMeter bandwidthMeter3g = + new ExperimentalBandwidthMeter.Builder(ApplicationProvider.getApplicationContext()).build(); + long initialEstimate3g = bandwidthMeter3g.getBitrateEstimate(); + + assertThat(initialEstimateWifi).isGreaterThan(initialEstimate3g); + } + + @Test + public void defaultInitialBitrateEstimate_forEthernet_isGreaterThanEstimateFor2G() { + setActiveNetworkInfo(networkInfoEthernet); + ExperimentalBandwidthMeter bandwidthMeterEthernet = + new ExperimentalBandwidthMeter.Builder(ApplicationProvider.getApplicationContext()).build(); + long initialEstimateEthernet = bandwidthMeterEthernet.getBitrateEstimate(); + + setActiveNetworkInfo(networkInfo2g); + ExperimentalBandwidthMeter bandwidthMeter2g = + new ExperimentalBandwidthMeter.Builder(ApplicationProvider.getApplicationContext()).build(); + long initialEstimate2g = bandwidthMeter2g.getBitrateEstimate(); + + assertThat(initialEstimateEthernet).isGreaterThan(initialEstimate2g); + } + + @Test + public void defaultInitialBitrateEstimate_forEthernet_isGreaterThanEstimateFor3G() { + setActiveNetworkInfo(networkInfoEthernet); + ExperimentalBandwidthMeter bandwidthMeterEthernet = + new ExperimentalBandwidthMeter.Builder(ApplicationProvider.getApplicationContext()).build(); + long initialEstimateEthernet = bandwidthMeterEthernet.getBitrateEstimate(); + + setActiveNetworkInfo(networkInfo3g); + ExperimentalBandwidthMeter bandwidthMeter3g = + new ExperimentalBandwidthMeter.Builder(ApplicationProvider.getApplicationContext()).build(); + long initialEstimate3g = bandwidthMeter3g.getBitrateEstimate(); + + assertThat(initialEstimateEthernet).isGreaterThan(initialEstimate3g); + } + + @Test + public void defaultInitialBitrateEstimate_for4G_isGreaterThanEstimateFor2G() { + setActiveNetworkInfo(networkInfo4g); + ExperimentalBandwidthMeter bandwidthMeter4g = + new ExperimentalBandwidthMeter.Builder(ApplicationProvider.getApplicationContext()).build(); + long initialEstimate4g = bandwidthMeter4g.getBitrateEstimate(); + + setActiveNetworkInfo(networkInfo2g); + ExperimentalBandwidthMeter bandwidthMeter2g = + new ExperimentalBandwidthMeter.Builder(ApplicationProvider.getApplicationContext()).build(); + long initialEstimate2g = bandwidthMeter2g.getBitrateEstimate(); + + assertThat(initialEstimate4g).isGreaterThan(initialEstimate2g); + } + + @Test + public void defaultInitialBitrateEstimate_for4G_isGreaterThanEstimateFor3G() { + setActiveNetworkInfo(networkInfo4g); + ExperimentalBandwidthMeter bandwidthMeter4g = + new ExperimentalBandwidthMeter.Builder(ApplicationProvider.getApplicationContext()).build(); + long initialEstimate4g = bandwidthMeter4g.getBitrateEstimate(); + + setActiveNetworkInfo(networkInfo3g); + ExperimentalBandwidthMeter bandwidthMeter3g = + new ExperimentalBandwidthMeter.Builder(ApplicationProvider.getApplicationContext()).build(); + long initialEstimate3g = bandwidthMeter3g.getBitrateEstimate(); + + assertThat(initialEstimate4g).isGreaterThan(initialEstimate3g); + } + + @Test + public void defaultInitialBitrateEstimate_for3G_isGreaterThanEstimateFor2G() { + setActiveNetworkInfo(networkInfo3g); + ExperimentalBandwidthMeter bandwidthMeter3g = + new ExperimentalBandwidthMeter.Builder(ApplicationProvider.getApplicationContext()).build(); + long initialEstimate3g = bandwidthMeter3g.getBitrateEstimate(); + + setActiveNetworkInfo(networkInfo2g); + ExperimentalBandwidthMeter bandwidthMeter2g = + new ExperimentalBandwidthMeter.Builder(ApplicationProvider.getApplicationContext()).build(); + long initialEstimate2g = bandwidthMeter2g.getBitrateEstimate(); + + assertThat(initialEstimate3g).isGreaterThan(initialEstimate2g); + } + + @Test + public void defaultInitialBitrateEstimate_forOffline_isReasonable() { + setActiveNetworkInfo(networkInfoOffline); + ExperimentalBandwidthMeter bandwidthMeter = + new ExperimentalBandwidthMeter.Builder(ApplicationProvider.getApplicationContext()).build(); + long initialEstimate = bandwidthMeter.getBitrateEstimate(); + + assertThat(initialEstimate).isGreaterThan(100_000L); + assertThat(initialEstimate).isLessThan(50_000_000L); + } + + @Test + public void + defaultInitialBitrateEstimate_forWifi_forFastCountry_isGreaterThanEstimateForSlowCountry() { + setActiveNetworkInfo(networkInfoWifi); + setNetworkCountryIso(FAST_COUNTRY_ISO); + ExperimentalBandwidthMeter bandwidthMeterFast = + new ExperimentalBandwidthMeter.Builder(ApplicationProvider.getApplicationContext()).build(); + long initialEstimateFast = bandwidthMeterFast.getBitrateEstimate(); + + setNetworkCountryIso(SLOW_COUNTRY_ISO); + ExperimentalBandwidthMeter bandwidthMeterSlow = + new ExperimentalBandwidthMeter.Builder(ApplicationProvider.getApplicationContext()).build(); + long initialEstimateSlow = bandwidthMeterSlow.getBitrateEstimate(); + + assertThat(initialEstimateFast).isGreaterThan(initialEstimateSlow); + } + + @Test + public void + defaultInitialBitrateEstimate_forEthernet_forFastCountry_isGreaterThanEstimateForSlowCountry() { + setActiveNetworkInfo(networkInfoEthernet); + setNetworkCountryIso(FAST_COUNTRY_ISO); + ExperimentalBandwidthMeter bandwidthMeterFast = + new ExperimentalBandwidthMeter.Builder(ApplicationProvider.getApplicationContext()).build(); + long initialEstimateFast = bandwidthMeterFast.getBitrateEstimate(); + + setNetworkCountryIso(SLOW_COUNTRY_ISO); + ExperimentalBandwidthMeter bandwidthMeterSlow = + new ExperimentalBandwidthMeter.Builder(ApplicationProvider.getApplicationContext()).build(); + long initialEstimateSlow = bandwidthMeterSlow.getBitrateEstimate(); + + assertThat(initialEstimateFast).isGreaterThan(initialEstimateSlow); + } + + @Test + public void + defaultInitialBitrateEstimate_for2G_forFastCountry_isGreaterThanEstimateForSlowCountry() { + setActiveNetworkInfo(networkInfo2g); + setNetworkCountryIso(FAST_COUNTRY_ISO); + ExperimentalBandwidthMeter bandwidthMeterFast = + new ExperimentalBandwidthMeter.Builder(ApplicationProvider.getApplicationContext()).build(); + long initialEstimateFast = bandwidthMeterFast.getBitrateEstimate(); + + setNetworkCountryIso(SLOW_COUNTRY_ISO); + ExperimentalBandwidthMeter bandwidthMeterSlow = + new ExperimentalBandwidthMeter.Builder(ApplicationProvider.getApplicationContext()).build(); + long initialEstimateSlow = bandwidthMeterSlow.getBitrateEstimate(); + + assertThat(initialEstimateFast).isGreaterThan(initialEstimateSlow); + } + + @Test + public void + defaultInitialBitrateEstimate_for3G_forFastCountry_isGreaterThanEstimateForSlowCountry() { + setActiveNetworkInfo(networkInfo3g); + setNetworkCountryIso(FAST_COUNTRY_ISO); + ExperimentalBandwidthMeter bandwidthMeterFast = + new ExperimentalBandwidthMeter.Builder(ApplicationProvider.getApplicationContext()).build(); + long initialEstimateFast = bandwidthMeterFast.getBitrateEstimate(); + + setNetworkCountryIso(SLOW_COUNTRY_ISO); + ExperimentalBandwidthMeter bandwidthMeterSlow = + new ExperimentalBandwidthMeter.Builder(ApplicationProvider.getApplicationContext()).build(); + long initialEstimateSlow = bandwidthMeterSlow.getBitrateEstimate(); + + assertThat(initialEstimateFast).isGreaterThan(initialEstimateSlow); + } + + @Test + public void + defaultInitialBitrateEstimate_for4g_forFastCountry_isGreaterThanEstimateForSlowCountry() { + setActiveNetworkInfo(networkInfo4g); + setNetworkCountryIso(FAST_COUNTRY_ISO); + ExperimentalBandwidthMeter bandwidthMeterFast = + new ExperimentalBandwidthMeter.Builder(ApplicationProvider.getApplicationContext()).build(); + long initialEstimateFast = bandwidthMeterFast.getBitrateEstimate(); + + setNetworkCountryIso(SLOW_COUNTRY_ISO); + ExperimentalBandwidthMeter bandwidthMeterSlow = + new ExperimentalBandwidthMeter.Builder(ApplicationProvider.getApplicationContext()).build(); + long initialEstimateSlow = bandwidthMeterSlow.getBitrateEstimate(); + + assertThat(initialEstimateFast).isGreaterThan(initialEstimateSlow); + } + + @Test + public void initialBitrateEstimateOverwrite_whileConnectedToNetwork_setsInitialEstimate() { + setActiveNetworkInfo(networkInfoWifi); + ExperimentalBandwidthMeter bandwidthMeter = + new ExperimentalBandwidthMeter.Builder(ApplicationProvider.getApplicationContext()) + .setInitialBitrateEstimate(123456789) + .build(); + long initialEstimate = bandwidthMeter.getBitrateEstimate(); + + assertThat(initialEstimate).isEqualTo(123456789); + } + + @Test + public void initialBitrateEstimateOverwrite_whileOffline_setsInitialEstimate() { + setActiveNetworkInfo(networkInfoOffline); + ExperimentalBandwidthMeter bandwidthMeter = + new ExperimentalBandwidthMeter.Builder(ApplicationProvider.getApplicationContext()) + .setInitialBitrateEstimate(123456789) + .build(); + long initialEstimate = bandwidthMeter.getBitrateEstimate(); + + assertThat(initialEstimate).isEqualTo(123456789); + } + + @Test + public void initialBitrateEstimateOverwrite_forWifi_whileConnectedToWifi_setsInitialEstimate() { + setActiveNetworkInfo(networkInfoWifi); + ExperimentalBandwidthMeter bandwidthMeter = + new ExperimentalBandwidthMeter.Builder(ApplicationProvider.getApplicationContext()) + .setInitialBitrateEstimate(C.NETWORK_TYPE_WIFI, 123456789) + .build(); + long initialEstimate = bandwidthMeter.getBitrateEstimate(); + + assertThat(initialEstimate).isEqualTo(123456789); + } + + @Test + public void + initialBitrateEstimateOverwrite_forWifi_whileConnectedToOtherNetwork_doesNotSetInitialEstimate() { + setActiveNetworkInfo(networkInfo2g); + ExperimentalBandwidthMeter bandwidthMeter = + new ExperimentalBandwidthMeter.Builder(ApplicationProvider.getApplicationContext()) + .setInitialBitrateEstimate(C.NETWORK_TYPE_WIFI, 123456789) + .build(); + long initialEstimate = bandwidthMeter.getBitrateEstimate(); + + assertThat(initialEstimate).isNotEqualTo(123456789); + } + + @Test + public void + initialBitrateEstimateOverwrite_forEthernet_whileConnectedToEthernet_setsInitialEstimate() { + setActiveNetworkInfo(networkInfoEthernet); + ExperimentalBandwidthMeter bandwidthMeter = + new ExperimentalBandwidthMeter.Builder(ApplicationProvider.getApplicationContext()) + .setInitialBitrateEstimate(C.NETWORK_TYPE_ETHERNET, 123456789) + .build(); + long initialEstimate = bandwidthMeter.getBitrateEstimate(); + + assertThat(initialEstimate).isEqualTo(123456789); + } + + @Test + public void + initialBitrateEstimateOverwrite_forEthernet_whileConnectedToOtherNetwork_doesNotSetInitialEstimate() { + setActiveNetworkInfo(networkInfo2g); + ExperimentalBandwidthMeter bandwidthMeter = + new ExperimentalBandwidthMeter.Builder(ApplicationProvider.getApplicationContext()) + .setInitialBitrateEstimate(C.NETWORK_TYPE_WIFI, 123456789) + .build(); + long initialEstimate = bandwidthMeter.getBitrateEstimate(); + + assertThat(initialEstimate).isNotEqualTo(123456789); + } + + @Test + public void initialBitrateEstimateOverwrite_for2G_whileConnectedTo2G_setsInitialEstimate() { + setActiveNetworkInfo(networkInfo2g); + ExperimentalBandwidthMeter bandwidthMeter = + new ExperimentalBandwidthMeter.Builder(ApplicationProvider.getApplicationContext()) + .setInitialBitrateEstimate(C.NETWORK_TYPE_2G, 123456789) + .build(); + long initialEstimate = bandwidthMeter.getBitrateEstimate(); + + assertThat(initialEstimate).isEqualTo(123456789); + } + + @Test + public void + initialBitrateEstimateOverwrite_for2G_whileConnectedToOtherNetwork_doesNotSetInitialEstimate() { + setActiveNetworkInfo(networkInfoWifi); + ExperimentalBandwidthMeter bandwidthMeter = + new ExperimentalBandwidthMeter.Builder(ApplicationProvider.getApplicationContext()) + .setInitialBitrateEstimate(C.NETWORK_TYPE_2G, 123456789) + .build(); + long initialEstimate = bandwidthMeter.getBitrateEstimate(); + + assertThat(initialEstimate).isNotEqualTo(123456789); + } + + @Test + public void initialBitrateEstimateOverwrite_for3G_whileConnectedTo3G_setsInitialEstimate() { + setActiveNetworkInfo(networkInfo3g); + ExperimentalBandwidthMeter bandwidthMeter = + new ExperimentalBandwidthMeter.Builder(ApplicationProvider.getApplicationContext()) + .setInitialBitrateEstimate(C.NETWORK_TYPE_3G, 123456789) + .build(); + long initialEstimate = bandwidthMeter.getBitrateEstimate(); + + assertThat(initialEstimate).isEqualTo(123456789); + } + + @Test + public void + initialBitrateEstimateOverwrite_for3G_whileConnectedToOtherNetwork_doesNotSetInitialEstimate() { + setActiveNetworkInfo(networkInfoWifi); + ExperimentalBandwidthMeter bandwidthMeter = + new ExperimentalBandwidthMeter.Builder(ApplicationProvider.getApplicationContext()) + .setInitialBitrateEstimate(C.NETWORK_TYPE_3G, 123456789) + .build(); + long initialEstimate = bandwidthMeter.getBitrateEstimate(); + + assertThat(initialEstimate).isNotEqualTo(123456789); + } + + @Test + public void initialBitrateEstimateOverwrite_for4G_whileConnectedTo4G_setsInitialEstimate() { + setActiveNetworkInfo(networkInfo4g); + ExperimentalBandwidthMeter bandwidthMeter = + new ExperimentalBandwidthMeter.Builder(ApplicationProvider.getApplicationContext()) + .setInitialBitrateEstimate(C.NETWORK_TYPE_4G, 123456789) + .build(); + long initialEstimate = bandwidthMeter.getBitrateEstimate(); + + assertThat(initialEstimate).isEqualTo(123456789); + } + + @Test + public void + initialBitrateEstimateOverwrite_for4G_whileConnectedToOtherNetwork_doesNotSetInitialEstimate() { + setActiveNetworkInfo(networkInfoWifi); + ExperimentalBandwidthMeter bandwidthMeter = + new ExperimentalBandwidthMeter.Builder(ApplicationProvider.getApplicationContext()) + .setInitialBitrateEstimate(C.NETWORK_TYPE_4G, 123456789) + .build(); + long initialEstimate = bandwidthMeter.getBitrateEstimate(); + + assertThat(initialEstimate).isNotEqualTo(123456789); + } + + @Test + public void initialBitrateEstimateOverwrite_forOffline_whileOffline_setsInitialEstimate() { + setActiveNetworkInfo(networkInfoOffline); + ExperimentalBandwidthMeter bandwidthMeter = + new ExperimentalBandwidthMeter.Builder(ApplicationProvider.getApplicationContext()) + .setInitialBitrateEstimate(C.NETWORK_TYPE_OFFLINE, 123456789) + .build(); + long initialEstimate = bandwidthMeter.getBitrateEstimate(); + + assertThat(initialEstimate).isEqualTo(123456789); + } + + @Test + public void + initialBitrateEstimateOverwrite_forOffline_whileConnectedToNetwork_doesNotSetInitialEstimate() { + setActiveNetworkInfo(networkInfoWifi); + ExperimentalBandwidthMeter bandwidthMeter = + new ExperimentalBandwidthMeter.Builder(ApplicationProvider.getApplicationContext()) + .setInitialBitrateEstimate(C.NETWORK_TYPE_OFFLINE, 123456789) + .build(); + long initialEstimate = bandwidthMeter.getBitrateEstimate(); + + assertThat(initialEstimate).isNotEqualTo(123456789); + } + + @Test + public void initialBitrateEstimateOverwrite_forCountry_usesDefaultValuesForCountry() { + setNetworkCountryIso(SLOW_COUNTRY_ISO); + ExperimentalBandwidthMeter bandwidthMeterSlow = + new ExperimentalBandwidthMeter.Builder(ApplicationProvider.getApplicationContext()).build(); + long initialEstimateSlow = bandwidthMeterSlow.getBitrateEstimate(); + + setNetworkCountryIso(FAST_COUNTRY_ISO); + ExperimentalBandwidthMeter bandwidthMeterFastWithSlowOverwrite = + new ExperimentalBandwidthMeter.Builder(ApplicationProvider.getApplicationContext()) + .setInitialBitrateEstimate(SLOW_COUNTRY_ISO) + .build(); + long initialEstimateFastWithSlowOverwrite = + bandwidthMeterFastWithSlowOverwrite.getBitrateEstimate(); + + assertThat(initialEstimateFastWithSlowOverwrite).isEqualTo(initialEstimateSlow); + } + + @Test + public void networkTypeOverride_updatesBitrateEstimate() { + setActiveNetworkInfo(networkInfoEthernet); + ExperimentalBandwidthMeter bandwidthMeter = + new ExperimentalBandwidthMeter.Builder(ApplicationProvider.getApplicationContext()).build(); + long initialEstimateEthernet = bandwidthMeter.getBitrateEstimate(); + + bandwidthMeter.setNetworkTypeOverride(C.NETWORK_TYPE_2G); + long initialEstimate2g = bandwidthMeter.getBitrateEstimate(); + + assertThat(initialEstimateEthernet).isGreaterThan(initialEstimate2g); + } + + @Test + public void networkTypeOverride_doesFullReset() { + // Simulate transfers for an ethernet connection. + setActiveNetworkInfo(networkInfoEthernet); + FakeClock clock = new FakeClock(/* initialTimeMs= */ 0); + ExperimentalBandwidthMeter bandwidthMeter = + new ExperimentalBandwidthMeter.Builder(ApplicationProvider.getApplicationContext()) + .setClock(clock) + .build(); + long[] bitrateEstimatesWithNewInstance = simulateTransfers(bandwidthMeter, clock); + + // Create a new instance and seed with some transfers. + setActiveNetworkInfo(networkInfo2g); + bandwidthMeter = + new ExperimentalBandwidthMeter.Builder(ApplicationProvider.getApplicationContext()) + .setClock(clock) + .build(); + simulateTransfers(bandwidthMeter, clock); + + // Override the network type to ethernet and simulate transfers again. + bandwidthMeter.setNetworkTypeOverride(C.NETWORK_TYPE_ETHERNET); + long[] bitrateEstimatesAfterReset = simulateTransfers(bandwidthMeter, clock); + + // If overriding the network type fully reset the bandwidth meter, we expect the bitrate + // estimates generated during simulation to be the same. + assertThat(bitrateEstimatesAfterReset).isEqualTo(bitrateEstimatesWithNewInstance); + } + + @Test + public void defaultInitialBitrateEstimate_withoutContext_isReasonable() { + ExperimentalBandwidthMeter bandwidthMeterWithBuilder = + new ExperimentalBandwidthMeter.Builder(/* context= */ null).build(); + long initialEstimateWithBuilder = bandwidthMeterWithBuilder.getBitrateEstimate(); + + assertThat(initialEstimateWithBuilder).isGreaterThan(100_000L); + assertThat(initialEstimateWithBuilder).isLessThan(50_000_000L); + } + + private void setActiveNetworkInfo(NetworkInfo networkInfo) { + Shadows.shadowOf(connectivityManager).setActiveNetworkInfo(networkInfo); + } + + private void setNetworkCountryIso(String countryIso) { + Shadows.shadowOf(telephonyManager).setNetworkCountryIso(countryIso); + } + + private static long[] simulateTransfers( + ExperimentalBandwidthMeter bandwidthMeter, FakeClock clock) { + long[] bitrateEstimates = new long[SIMULATED_TRANSFER_COUNT]; + Random random = new Random(/* seed= */ 0); + DataSource dataSource = new FakeDataSource(); + DataSpec dataSpec = new DataSpec(Uri.parse("https://test.com")); + for (int i = 0; i < SIMULATED_TRANSFER_COUNT; i++) { + bandwidthMeter.onTransferStart(dataSource, dataSpec, /* isNetwork= */ true); + clock.advanceTime(random.nextInt(/* bound= */ 5000)); + bandwidthMeter.onBytesTransferred( + dataSource, + dataSpec, + /* isNetwork= */ true, + /* bytes= */ random.nextInt(5 * 1024 * 1024)); + bandwidthMeter.onTransferEnd(dataSource, dataSpec, /* isNetwork= */ true); + bitrateEstimates[i] = bandwidthMeter.getBitrateEstimate(); + } + return bitrateEstimates; + } +} diff --git a/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java b/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java index 8acc33e640..705188f88e 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java @@ -364,7 +364,6 @@ public final class CacheDataSourceTest { new CacheWriter( new CacheDataSource(cache, upstream2), unboundedDataSpec, - /* allowShortContent= */ false, /* temporaryBuffer= */ null, /* progressListener= */ null); cacheWriter.cache(); @@ -414,7 +413,6 @@ public final class CacheDataSourceTest { new CacheWriter( new CacheDataSource(cache, upstream2), unboundedDataSpec, - /* allowShortContent= */ false, /* temporaryBuffer= */ null, /* progressListener= */ null); cacheWriter.cache(); @@ -439,7 +437,6 @@ public final class CacheDataSourceTest { new CacheWriter( new CacheDataSource(cache, upstream), dataSpec, - /* allowShortContent= */ false, /* temporaryBuffer= */ null, /* progressListener= */ null); cacheWriter.cache(); @@ -476,7 +473,6 @@ public final class CacheDataSourceTest { new CacheWriter( new CacheDataSource(cache, upstream), dataSpec, - /* allowShortContent= */ false, /* temporaryBuffer= */ null, /* progressListener= */ null); cacheWriter.cache(); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheWriterTest.java b/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheWriterTest.java index 26c856b1a0..b094f332de 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheWriterTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheWriterTest.java @@ -30,7 +30,6 @@ import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.FileDataSource; import com.google.android.exoplayer2.util.Util; -import java.io.EOFException; import java.io.File; import java.io.IOException; import java.util.concurrent.atomic.AtomicBoolean; @@ -72,7 +71,6 @@ public final class CacheWriterTest { new CacheWriter( new CacheDataSource(cache, dataSource), new DataSpec(Uri.parse("test_data")), - /* allowShortContent= */ false, /* temporaryBuffer= */ null, counters); cacheWriter.cache(); @@ -94,7 +92,6 @@ public final class CacheWriterTest { new CacheWriter( new CacheDataSource(cache, dataSource), dataSpec, - /* allowShortContent= */ false, /* temporaryBuffer= */ null, counters); cacheWriter.cache(); @@ -106,7 +103,6 @@ public final class CacheWriterTest { new CacheWriter( new CacheDataSource(cache, dataSource), new DataSpec(testUri), - /* allowShortContent= */ false, /* temporaryBuffer= */ null, counters); cacheWriter.cache(); @@ -129,7 +125,6 @@ public final class CacheWriterTest { new CacheWriter( new CacheDataSource(cache, dataSource), dataSpec, - /* allowShortContent= */ false, /* temporaryBuffer= */ null, counters); cacheWriter.cache(); @@ -153,7 +148,6 @@ public final class CacheWriterTest { new CacheWriter( new CacheDataSource(cache, dataSource), dataSpec, - /* allowShortContent= */ false, /* temporaryBuffer= */ null, counters); cacheWriter.cache(); @@ -165,7 +159,6 @@ public final class CacheWriterTest { new CacheWriter( new CacheDataSource(cache, dataSource), new DataSpec(testUri), - /* allowShortContent= */ false, /* temporaryBuffer= */ null, counters); cacheWriter.cache(); @@ -187,7 +180,6 @@ public final class CacheWriterTest { new CacheWriter( new CacheDataSource(cache, dataSource), dataSpec, - /* allowShortContent= */ true, /* temporaryBuffer= */ null, counters); cacheWriter.cache(); @@ -196,24 +188,6 @@ public final class CacheWriterTest { assertCachedData(cache, fakeDataSet); } - @Test - public void cacheThrowEOFException() throws Exception { - FakeDataSet fakeDataSet = new FakeDataSet().setRandomData("test_data", 100); - FakeDataSource dataSource = new FakeDataSource(fakeDataSet); - - Uri testUri = Uri.parse("test_data"); - DataSpec dataSpec = new DataSpec(testUri, /* position= */ 0, /* length= */ 1000); - - CacheWriter cacheWriter = - new CacheWriter( - new CacheDataSource(cache, dataSource), - dataSpec, - /* allowShortContent= */ false, - /* temporaryBuffer= */ null, - /* progressListener= */ null); - assertThrows(EOFException.class, cacheWriter::cache); - } - @Test public void cache_afterFailureOnClose_succeeds() throws Exception { FakeDataSet fakeDataSet = new FakeDataSet().setRandomData("test_data", 100); @@ -237,7 +211,6 @@ public final class CacheWriterTest { new CacheWriter( cacheDataSource, new DataSpec(Uri.parse("test_data")), - /* allowShortContent= */ false, /* temporaryBuffer= */ null, counters); @@ -276,7 +249,6 @@ public final class CacheWriterTest { new CacheWriter( new CacheDataSource(cache, dataSource), new DataSpec(Uri.parse("test_data")), - /* allowShortContent= */ false, /* temporaryBuffer= */ null, counters); cacheWriter.cache(); diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/DataSourceContractTest.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/DataSourceContractTest.java index 5a15f46919..bf39e8e309 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/DataSourceContractTest.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/DataSourceContractTest.java @@ -232,24 +232,27 @@ public abstract class DataSourceContractTest { DataSpec dataSpec = new DataSpec.Builder().setUri(resource.getUri()).setPosition(resourceLength).build(); try { - long length = dataSource.open(dataSpec); - // The DataSource.open() contract requires the returned length to equal the length in the - // DataSpec if set. This is true even though the DataSource implementation may know that - // fewer bytes will be read in this case. - if (length != C.LENGTH_UNSET) { - assertThat(length).isEqualTo(0); - } - try { - byte[] data = - unboundedReadsAreIndefinite() ? Util.EMPTY_BYTE_ARRAY : Util.readToEnd(dataSource); - assertThat(data).isEmpty(); + long length = dataSource.open(dataSpec); + // The DataSource.open() contract requires the returned length to equal the length in the + // DataSpec if set. This is true even though the DataSource implementation may know that + // fewer bytes will be read in this case. + if (length != C.LENGTH_UNSET) { + assertThat(length).isEqualTo(0); + } + + try { + byte[] data = + unboundedReadsAreIndefinite() ? Util.EMPTY_BYTE_ARRAY : Util.readToEnd(dataSource); + assertThat(data).isEmpty(); + } catch (IOException e) { + // TODO: Remove this catch once the one below is removed. + throw new RuntimeException(e); + } } catch (IOException e) { // TODO: Remove this catch and require that implementations do not throw. + assertThat(DataSourceException.isCausedByPositionOutOfRange(e)).isTrue(); } - } catch (IOException e) { - // TODO: Remove this catch and require that implementations do not throw. - assertThat(DataSourceException.isCausedByPositionOutOfRange(e)).isTrue(); } finally { dataSource.close(); } @@ -275,22 +278,25 @@ public abstract class DataSourceContractTest { .setLength(1) .build(); try { - long length = dataSource.open(dataSpec); - // The DataSource.open() contract requires the returned length to equal the length in the - // DataSpec if set. This is true even though the DataSource implementation may know that - // fewer bytes will be read in this case. - assertThat(length).isEqualTo(1); - try { - byte[] data = - unboundedReadsAreIndefinite() ? Util.EMPTY_BYTE_ARRAY : Util.readToEnd(dataSource); - assertThat(data).isEmpty(); + long length = dataSource.open(dataSpec); + // The DataSource.open() contract requires the returned length to equal the length in the + // DataSpec if set. This is true even though the DataSource implementation may know that + // fewer bytes will be read in this case. + assertThat(length).isEqualTo(1); + + try { + byte[] data = + unboundedReadsAreIndefinite() ? Util.EMPTY_BYTE_ARRAY : Util.readToEnd(dataSource); + assertThat(data).isEmpty(); + } catch (IOException e) { + // TODO: Remove this catch once the one below is removed. + throw new RuntimeException(e); + } } catch (IOException e) { // TODO: Remove this catch and require that implementations do not throw. + assertThat(DataSourceException.isCausedByPositionOutOfRange(e)).isTrue(); } - } catch (IOException e) { - // TODO: Remove this catch and require that implementations do not throw. - assertThat(DataSourceException.isCausedByPositionOutOfRange(e)).isTrue(); } finally { dataSource.close(); }