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
This commit is contained in:
tonihei 2021-03-18 16:07:01 +00:00 committed by Ian Baker
parent 1affbf9357
commit f8727b5e87
4 changed files with 128 additions and 102 deletions

View file

@ -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<String, String> createIsoLanguageReplacementMap() {
String[] iso2Languages = Locale.getISOLanguages();
HashMap<String, String> replacedLanguages =

View file

@ -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;
}

View file

@ -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<WeakReference<Listener>> 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<Listener> 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<Listener> 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);
}
}
}

View file

@ -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) {