From f8727b5e875e4e8e7abe3bfebd2c101dd41541aa Mon Sep 17 00:00:00 2001 From: tonihei Date: Thu, 18 Mar 2021 16:07:01 +0000 Subject: [PATCH] Move Util.getNetworkType into NetworkTypeObserver. The main user of the Util method is the bandwidth meter to set the initial network type. If this is the first call to the NetworkTypeObserver, then we should also allow the first update to a known network type even if no reset on network type is activated. The other uses are analytics listeners that check the network type at certain events. This can just use the lookup method of the NetworkTypeObserver. PiperOrigin-RevId: 363670771 --- .../google/android/exoplayer2/util/Util.java | 80 ----------- .../upstream/DefaultBandwidthMeter.java | 18 ++- .../exoplayer2/util/NetworkTypeObserver.java | 124 +++++++++++++++--- .../upstream/DefaultBandwidthMeterTest.java | 8 ++ 4 files changed, 128 insertions(+), 102 deletions(-) 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) {