Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android - Why ConnectivityManager.isActiveNetworkMetered always returning true (even when on Wifi)

In my music streaming app for Android, the code path below helps decides between a high bitrate stream vs. a lower bitrate based on values returned by the ConnectivityManager class instance.

I am trying to understand why ConnectivityManager.isActiveNetworkMetered() would return "true", when it's evident I'm on a local Wifi network. Take the following Android/Java code:

boolean isMetered = false;
boolean isWifi = false;
boolean isEthernet = false;
boolean isRoaming = false;
boolean isConnected = false;

NetworkInfo netinfo = connManager.getActiveNetworkInfo();
if (netinfo != null)
{
    isWifi = (netinfo.getType() == ConnectivityManager.TYPE_WIFI);
    isEthernet = (netinfo.getType() == ConnectivityManager.TYPE_ETHERNET);
    isRoaming = netinfo.isRoaming();
    isConnected = netinfo.isConnected();

    Log.d(TAG, "active network type = " + netinfo.getTypeName());
    Log.d(TAG, "active network subtype = " + netinfo.getSubtypeName());


    Log.d(TAG, "isConnected = " + isConnected);
    Log.d(TAG, "isWifi = " + isWifi);
    Log.d(TAG, "isEthernet = " + isEthernet);
    Log.d(TAG, "isRoaming = " + isRoaming);
}

// isActiveNetworkMetered was introduced in API 16 (Jelly Bean)
if (android.os.Build.VERSION.SDK_INT >= 16)
{
    isMetered = connManager.isActiveNetworkMetered();
    Log.d(TAG, "isMetered = " + isMetered);
}

When I have Wifi turned off and my only connection is to AT&T's LTE network, it prints out the following to the log console:

active network type = mobile
active network subtype = LTE
isConnected = true
isWifi = false
isEthernet = false
isRoaming = false
isMetered = true

Everything above matches expectations - I'm on a mobile carrier network that is metered.

Now switch to my home Wifi and the same block of code prints this:

active network type = WIFI
active network subtype = 
isConnected = true
isWifi = true
isEthernet = false
isRoaming = false
isMetered = true

Why is isMetered, the result of isActiveNetworkMetered, still showing as true? This is causing the following code path to favor the lower bitrate stream even though I'm on my home wifi network:

if ((isWifi || isEthernet) && !isMetered)
{
    result = BITRATE_HIGH_KBIT_SEC;
}
else
{
    result = BITRATE_LOW_KBIT_SEC;
}

How does isActiveNetworkMetered() decide if the network is metered? Is this part of the WIFI negotiation or a bit in the SSID broadcast? A network setting on Android? I couldn't find any setting on my Android Kitkat device that let me toggle or discover the setting for a metered network.

Last week I my ISP (Frontier) sent me a new Wifi router. Possibly related? I didn't see anything on the router's firmware pages for such a setting.

I'm going to swing by work later today to see how it behaves on another network. In any case, I'm likely to edit the above code to skip the metered check unless I can prove its just an incorrect setting somewhere.

UPDATE - issue of always returning metered network reproduces on my HTC One M8 phone (running Kitkat), but not on my Nexus 7 from 2012 (also upgraded to Android 4.4 Kitkat).

PROBLEM SOLVED - it turns out my Wifi was flagged by my phone as a "Mobile Hotspot". More details on how to find and toggle this flag is here.

like image 225
selbie Avatar asked May 26 '14 20:05

selbie


1 Answers

I've tested this too, but I'm observing the "expected" result: false for WiFi and true for 3G. This is in a Nexus 4 with Android 4.4.2.

Curiously enough the ConnectivityManagerCompat class in the support library does return false for WiFi.

final int type = info.getType();
switch (type) {
    case TYPE_MOBILE:
        return true;
    case TYPE_WIFI:
        return false;
    default:
        // err on side of caution
        return true;
}

EDIT - Found it (I think)

NetworkPolicyManagerService seems to be the class that ultimately produces the result for this method. And according to it, WiFi connections can indeed be metered. It contains a BroadcastReceiver that "listen(s) for wifi state changes to catch metered hint" (line 567). This information is obtained from NetworkInfo.getMeteredHint(), which, on closer inspection, contains this interesting comment:

/**
 * Flag indicating that AP has hinted that upstream connection is metered,
 * and sensitive to heavy data transfers.
 */
private boolean mMeteredHint;

This flag is loaded from DhcpResults.hasMeteredHint()

/**
 * Test if this DHCP lease includes vendor hint that network link is
 * metered, and sensitive to heavy data transfers.
 */
public boolean hasMeteredHint() {
    if (vendorInfo != null) {
        return vendorInfo.contains("ANDROID_METERED");
    } else {
        return false;
    }
}

So it would seem that, indeed, the AP may notify its WiFi clients that the underlying internet connection is metered by using this flag.

EDIT #2 Relevant information, from http://www.lorier.net/docs/android-metered

When an android phone is using another android phone's hotspot, it knows it's on a "metered connection" and therefore disables the expensive sync options ( eg: photo sync). How does it know this? The android hotspot sends DHCP Option 43 (Vendor specific options) with the value ANDROID_METERED. The client, if it sees ANDROID_METERED anywhere in the option 43 values, turns on the "expensive data connection" option.

Looks like this flag was added to "play nice" with a hotspot offered by another Android device.

EDIT #3 The commit that introduced this feature:

Connect metered DHCP hint for Wi-Fi networks.

When DHCP lease includes vendor info indicating that remote Wi-Fi network is metered, advise NetworkPolicy. Users can still manually change the metered flag in Settings.

You can access this setting by going:

Settings -> Data Usage -> (Overflow Menu) -> Mobile Hotspots

like image 70
matiash Avatar answered Nov 20 '22 23:11

matiash