diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java b/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java index a17a9a742e..0dbce9a70f 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java @@ -37,8 +37,6 @@ import android.database.DatabaseUtils; import android.database.sqlite.SQLiteDatabase; import android.graphics.Point; import android.media.AudioFormat; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; import android.net.Uri; import android.os.Build; import android.os.Handler; @@ -2148,52 +2146,6 @@ public final class Util { return buffer.order() == ByteOrder.BIG_ENDIAN ? value : Integer.reverseBytes(value); } - /** - * Returns the {@link C.NetworkType} of the current network connection. - * - * @param context A context to access the connectivity manager. - * @return The {@link C.NetworkType} of the current network connection. - */ - // Intentional null check to guard against user input. - @SuppressWarnings("known.nonnull") - @C.NetworkType - public static int getNetworkType(Context context) { - if (context == null) { - // Note: This is for backward compatibility only (context used to be @Nullable). - return C.NETWORK_TYPE_UNKNOWN; - } - NetworkInfo networkInfo; - @Nullable - ConnectivityManager connectivityManager = - (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - if (connectivityManager == null) { - return C.NETWORK_TYPE_UNKNOWN; - } - try { - networkInfo = connectivityManager.getActiveNetworkInfo(); - } catch (SecurityException e) { - // Expected if permission was revoked. - return C.NETWORK_TYPE_UNKNOWN; - } - if (networkInfo == null || !networkInfo.isConnected()) { - return C.NETWORK_TYPE_OFFLINE; - } - switch (networkInfo.getType()) { - case ConnectivityManager.TYPE_WIFI: - return C.NETWORK_TYPE_WIFI; - case ConnectivityManager.TYPE_WIMAX: - return C.NETWORK_TYPE_4G; - case ConnectivityManager.TYPE_MOBILE: - case ConnectivityManager.TYPE_MOBILE_DUN: - case ConnectivityManager.TYPE_MOBILE_HIPRI: - return getMobileNetworkType(networkInfo); - case ConnectivityManager.TYPE_ETHERNET: - return C.NETWORK_TYPE_ETHERNET; - default: - return C.NETWORK_TYPE_OTHER; - } - } - /** * Returns the upper-case ISO 3166-1 alpha-2 country code of the current registered operator's MCC * (Mobile Country Code), or the country code of the default Locale if not available. @@ -2482,38 +2434,6 @@ public final class Util { return locale.toLanguageTag(); } - private static @C.NetworkType int getMobileNetworkType(NetworkInfo networkInfo) { - switch (networkInfo.getSubtype()) { - case TelephonyManager.NETWORK_TYPE_EDGE: - case TelephonyManager.NETWORK_TYPE_GPRS: - return C.NETWORK_TYPE_2G; - case TelephonyManager.NETWORK_TYPE_1xRTT: - case TelephonyManager.NETWORK_TYPE_CDMA: - case TelephonyManager.NETWORK_TYPE_EVDO_0: - case TelephonyManager.NETWORK_TYPE_EVDO_A: - case TelephonyManager.NETWORK_TYPE_EVDO_B: - case TelephonyManager.NETWORK_TYPE_HSDPA: - case TelephonyManager.NETWORK_TYPE_HSPA: - case TelephonyManager.NETWORK_TYPE_HSUPA: - case TelephonyManager.NETWORK_TYPE_IDEN: - case TelephonyManager.NETWORK_TYPE_UMTS: - case TelephonyManager.NETWORK_TYPE_EHRPD: - case TelephonyManager.NETWORK_TYPE_HSPAP: - case TelephonyManager.NETWORK_TYPE_TD_SCDMA: - return C.NETWORK_TYPE_3G; - case TelephonyManager.NETWORK_TYPE_LTE: - return C.NETWORK_TYPE_4G; - case TelephonyManager.NETWORK_TYPE_NR: - return SDK_INT >= 29 ? C.NETWORK_TYPE_5G : C.NETWORK_TYPE_UNKNOWN; - case TelephonyManager.NETWORK_TYPE_IWLAN: - return C.NETWORK_TYPE_WIFI; - case TelephonyManager.NETWORK_TYPE_GSM: - case TelephonyManager.NETWORK_TYPE_UNKNOWN: - default: // Future mobile network types. - return C.NETWORK_TYPE_CELLULAR_UNKNOWN; - } - } - private static HashMap createIsoLanguageReplacementMap() { String[] iso2Languages = Locale.getISOLanguages(); HashMap replacedLanguages = diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeter.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeter.java index c71dccd251..17f5f03a34 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeter.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeter.java @@ -259,6 +259,7 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList private final EventDispatcher eventDispatcher; private final SlidingPercentile slidingPercentile; private final Clock clock; + private final boolean resetOnNetworkTypeChange; private int streamCount; private long sampleStartTimeMs; @@ -294,13 +295,15 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList this.eventDispatcher = new EventDispatcher(); this.slidingPercentile = new SlidingPercentile(maxWeight); 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 network change information if possible. - if (context != null && resetOnNetworkTypeChange) { + this.resetOnNetworkTypeChange = resetOnNetworkTypeChange; + if (context != null) { NetworkTypeObserver networkTypeObserver = NetworkTypeObserver.getInstance(context); + networkType = networkTypeObserver.getNetworkType(); + bitrateEstimate = getInitialBitrateEstimateForNetworkType(networkType); networkTypeObserver.register(/* listener= */ this::onNetworkTypeChanged); + } else { + networkType = C.NETWORK_TYPE_UNKNOWN; + bitrateEstimate = getInitialBitrateEstimateForNetworkType(C.NETWORK_TYPE_UNKNOWN); } } @@ -391,6 +394,11 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList } private synchronized void onNetworkTypeChanged(@C.NetworkType int networkType) { + if (this.networkType != C.NETWORK_TYPE_UNKNOWN && !resetOnNetworkTypeChange) { + // Reset on network change disabled. Ignore all updates except the initial one. + return; + } + if (networkTypeOverrideSet) { networkType = networkTypeOverride; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/NetworkTypeObserver.java b/library/core/src/main/java/com/google/android/exoplayer2/util/NetworkTypeObserver.java index 198ab742df..c66bd94beb 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/NetworkTypeObserver.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/NetworkTypeObserver.java @@ -20,13 +20,16 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.net.ConnectivityManager; +import android.net.NetworkInfo; import android.os.Handler; import android.os.Looper; +import android.telephony.TelephonyManager; +import androidx.annotation.GuardedBy; import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import com.google.android.exoplayer2.C; import java.lang.ref.WeakReference; import java.util.concurrent.CopyOnWriteArrayList; -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** * Observer for network type changes. @@ -49,12 +52,16 @@ public final class NetworkTypeObserver { void onNetworkTypeChanged(@C.NetworkType int networkType); } - private static @MonotonicNonNull NetworkTypeObserver staticInstance; + @Nullable private static NetworkTypeObserver staticInstance; - private final Context context; private final Handler mainHandler; // This class needs to hold weak references as it doesn't require listeners to unregister. private final CopyOnWriteArrayList> listeners; + private final Object networkTypeLock; + + @GuardedBy("networkTypeLock") + @C.NetworkType + private int networkType; /** * Returns a network type observer instance. @@ -68,10 +75,17 @@ public final class NetworkTypeObserver { return staticInstance; } + /** Resets the network type observer for tests. */ + @VisibleForTesting + public static synchronized void resetForTests() { + staticInstance = null; + } + private NetworkTypeObserver(Context context) { - this.context = context.getApplicationContext(); mainHandler = new Handler(Looper.getMainLooper()); listeners = new CopyOnWriteArrayList<>(); + networkTypeLock = new Object(); + networkType = C.NETWORK_TYPE_UNKNOWN; IntentFilter filter = new IntentFilter(); filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); context.registerReceiver(/* receiver= */ new Receiver(), filter); @@ -95,7 +109,9 @@ public final class NetworkTypeObserver { /** Returns the current network type. */ @C.NetworkType public int getNetworkType() { - return Util.getNetworkType(context); + synchronized (networkTypeLock) { + return networkType; + } } private void removeClearedReferences() { @@ -106,22 +122,96 @@ public final class NetworkTypeObserver { } } + private void updateNetworkType(@C.NetworkType int networkType) { + synchronized (networkTypeLock) { + if (this.networkType == networkType) { + return; + } + this.networkType = networkType; + } + for (WeakReference listenerReference : listeners) { + @Nullable Listener listener = listenerReference.get(); + if (listener != null) { + listener.onNetworkTypeChanged(networkType); + } else { + listeners.remove(listenerReference); + } + } + } + + @C.NetworkType + private static int getNetworkTypeFromConnectivityManager(Context context) { + NetworkInfo networkInfo; + @Nullable + ConnectivityManager connectivityManager = + (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + if (connectivityManager == null) { + return C.NETWORK_TYPE_UNKNOWN; + } + try { + networkInfo = connectivityManager.getActiveNetworkInfo(); + } catch (SecurityException e) { + // Expected if permission was revoked. + return C.NETWORK_TYPE_UNKNOWN; + } + if (networkInfo == null || !networkInfo.isConnected()) { + return C.NETWORK_TYPE_OFFLINE; + } + switch (networkInfo.getType()) { + case ConnectivityManager.TYPE_WIFI: + return C.NETWORK_TYPE_WIFI; + case ConnectivityManager.TYPE_WIMAX: + return C.NETWORK_TYPE_4G; + case ConnectivityManager.TYPE_MOBILE: + case ConnectivityManager.TYPE_MOBILE_DUN: + case ConnectivityManager.TYPE_MOBILE_HIPRI: + return getMobileNetworkType(networkInfo); + case ConnectivityManager.TYPE_ETHERNET: + return C.NETWORK_TYPE_ETHERNET; + default: + return C.NETWORK_TYPE_OTHER; + } + } + + @C.NetworkType + private static int getMobileNetworkType(NetworkInfo networkInfo) { + switch (networkInfo.getSubtype()) { + case TelephonyManager.NETWORK_TYPE_EDGE: + case TelephonyManager.NETWORK_TYPE_GPRS: + return C.NETWORK_TYPE_2G; + case TelephonyManager.NETWORK_TYPE_1xRTT: + case TelephonyManager.NETWORK_TYPE_CDMA: + case TelephonyManager.NETWORK_TYPE_EVDO_0: + case TelephonyManager.NETWORK_TYPE_EVDO_A: + case TelephonyManager.NETWORK_TYPE_EVDO_B: + case TelephonyManager.NETWORK_TYPE_HSDPA: + case TelephonyManager.NETWORK_TYPE_HSPA: + case TelephonyManager.NETWORK_TYPE_HSUPA: + case TelephonyManager.NETWORK_TYPE_IDEN: + case TelephonyManager.NETWORK_TYPE_UMTS: + case TelephonyManager.NETWORK_TYPE_EHRPD: + case TelephonyManager.NETWORK_TYPE_HSPAP: + case TelephonyManager.NETWORK_TYPE_TD_SCDMA: + return C.NETWORK_TYPE_3G; + case TelephonyManager.NETWORK_TYPE_LTE: + return C.NETWORK_TYPE_4G; + case TelephonyManager.NETWORK_TYPE_NR: + return Util.SDK_INT >= 29 ? C.NETWORK_TYPE_5G : C.NETWORK_TYPE_UNKNOWN; + case TelephonyManager.NETWORK_TYPE_IWLAN: + return C.NETWORK_TYPE_WIFI; + case TelephonyManager.NETWORK_TYPE_GSM: + case TelephonyManager.NETWORK_TYPE_UNKNOWN: + default: // Future mobile network types. + return C.NETWORK_TYPE_CELLULAR_UNKNOWN; + } + } + private final class Receiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - if (isInitialStickyBroadcast()) { - return; - } - @C.NetworkType int networkType = getNetworkType(); - for (WeakReference listenerReference : listeners) { - @Nullable Listener listener = listenerReference.get(); - if (listener != null) { - listener.onNetworkTypeChanged(networkType); - } else { - listeners.remove(listenerReference); - } - } + @C.NetworkType int networkType = getNetworkTypeFromConnectivityManager(context); + updateNetworkType(networkType); } } } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeterTest.java b/library/core/src/test/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeterTest.java index 0b807c487a..8a2dbac7b9 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeterTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeterTest.java @@ -20,6 +20,7 @@ import static android.net.NetworkInfo.State.DISCONNECTED; import static com.google.common.truth.Truth.assertThat; import android.content.Context; +import android.content.Intent; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.NetworkInfo.DetailedState; @@ -30,11 +31,13 @@ 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 com.google.android.exoplayer2.util.NetworkTypeObserver; 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.ShadowLooper; import org.robolectric.shadows.ShadowNetworkInfo; /** Unit test for {@link DefaultBandwidthMeter}. */ @@ -56,6 +59,7 @@ public final class DefaultBandwidthMeterTest { @Before public void setUp() { + NetworkTypeObserver.resetForTests(); connectivityManager = (ConnectivityManager) ApplicationProvider.getApplicationContext() @@ -559,8 +563,12 @@ public final class DefaultBandwidthMeterTest { assertThat(initialEstimateWithoutBuilder).isLessThan(50_000_000L); } + @SuppressWarnings("StickyBroadcast") private void setActiveNetworkInfo(NetworkInfo networkInfo) { Shadows.shadowOf(connectivityManager).setActiveNetworkInfo(networkInfo); + ApplicationProvider.getApplicationContext() + .sendStickyBroadcast(new Intent(ConnectivityManager.CONNECTIVITY_ACTION)); + ShadowLooper.idleMainLooper(); } private void setNetworkCountryIso(String countryIso) {