android developer
android developer

Reputation: 116412

How to get network usage of apps on Android Q?

Background

I know that we can get the network usage (total bandwidth used of mobile&Wifi so far, from some specific time) of a specified app by using something like that (asked in the past, here) :

    private final static int[] NETWORKS_TYPES = new int[]{ConnectivityManager.TYPE_WIFI, ConnectivityManager.TYPE_MOBILE};

    long rxBytes=0L, txBytes=0L;
    final TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
    final String subscriberId = telephonyManager.getSubscriberId();
    final ApplicationInfo applicationInfo = context.getPackageManager().getApplicationInfo(packageName, 0);
    final int uid = applicationInfo.uid;
    for (int networkType : NETWORKS_TYPES) {
        final NetworkStats networkStats = networkStatsManager.queryDetailsForUid(networkType, subscriberId, 0, System.currentTimeMillis(), uid); 
        final Bucket bucketOut = new Bucket();
        while (true) {
                networkStats.getNextBucket(bucketOut);
                final long rxBytes = bucketOut.getRxBytes();
                if (rxBytes >= 0)
                    totalRx += rxBytes;
                final long txBytes = bucketOut.getTxBytes();
                if (txBytes >= 0)
                    totalTx += txBytes;
                if (!networkStats.hasNextBucket())
                    break;
            }
        }
    }

Docs:

https://developer.android.com/reference/android/app/usage/NetworkStatsManager.html#queryDetailsForUid(int,%20java.lang.String,%20long,%20long,%20int)

It's also possible to get the global network usage (using TrafficStats.getUidRxBytes(applicationInfo.uid) and TrafficStats.getUidTxBytes(applicationInfo.uid) ), but that's not what this thread is all about.

The problem

It seems Android Q is planned to cause a lot of device-identity functions to stop working anymore, and getSubscriberId is one of them.

What I've tried

I tried to set the targetSdk to 29 (Q) and see what happens when I try to use this.

As expected, I got an exception that shows me that I can't do it anymore. It says :

019-06-11 02:08:01.871 13558-13558/com.android.myapplication E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.android.myapplication, PID: 13558
    java.lang.SecurityException: getSubscriberId: The user 10872 does not meet the requirements to access device identifiers.
        at android.os.Parcel.createException(Parcel.java:2069)
        at android.os.Parcel.readException(Parcel.java:2037)
        at android.os.Parcel.readException(Parcel.java:1986)
        at com.android.internal.telephony.IPhoneSubInfo$Stub$Proxy.getSubscriberIdForSubscriber(IPhoneSubInfo.java:984)
        at android.telephony.TelephonyManager.getSubscriberId(TelephonyManager.java:3498)
        at android.telephony.TelephonyManager.getSubscriberId(TelephonyManager.java:3473)

Searching the Internet and here, I don't see this mentioned, but I have found about similar issues, of getting IMEI and other identifiers:

So for now I just made a bug report about it here (including a sample project) :

https://issuetracker.google.com/issues/134919382

The question

Is it possible to get network usage of a specified app on Android Q (when targeting to it) ? Maybe without subscriberId?

If so, how?

If not, is it possible by having root, or via adb?


EDIT:

OK, I don't know how to officially use this, but at least for root access it is possible to get the subscriberId, using this solution, found from here.

Meaning something like that:

@SuppressLint("MissingPermission", "HardwareIds")
fun getSubscriberId(telephonyManager: TelephonyManager): String? {
    try {
        return telephonyManager.subscriberId
    } catch (e: Exception) {
    }
    val commandResult = Root.runCommands("service call iphonesubinfo 1 | grep -o \"[0-9a-f]\\{8\\} \" | tail -n+3 | while read a; do echo -n \"\\u\${a:4:4}\\u\${a:0:4}\"; done")
    val subscriberId = commandResult?.getOrNull(0)
    return if (subscriberId.isNullOrBlank()) null else subscriberId
}

It's not an official solution, of course, but it's better than nothing...

EDIT: the part of getting it via root is wrong. It doesn't help in any way.

Upvotes: 10

Views: 3114

Answers (3)

Atakan Yıldırım
Atakan Yıldırım

Reputation: 892

You can provide null value for API 29 and above. It returns values ​​for both WIFI and Mobile Data.

Documentation:

If applicable, the subscriber id of the network interface. Starting with API level 29, the subscriberId is guarded by additional restrictions. Calling apps that do not meet the new requirements to access the subscriberId can provide a null value when querying for the mobile network type to receive usage for all mobile networks. For additional details see TelephonyManager.getSubscriberId().

Permissions (Don't forget to get permission from the user):

<uses-permission android:name="android.permission.READ_PHONE_STATE"
    android:maxSdkVersion="28"/>
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"
    tools:ignore="ProtectedPermissions" />

Example code:

//network stats
NetworkStatsManager networkStatsManager = (NetworkStatsManager) activity.getSystemService(Context.NETWORK_STATS_SERVICE);
int[] networkTypes = new int[]{NetworkCapabilities.TRANSPORT_CELLULAR, NetworkCapabilities.TRANSPORT_WIFI};
String subscriberId;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
   TelephonyManager telephonyManager = (TelephonyManager) activity.getSystemService(Context.TELEPHONY_SERVICE);
   try {
     subscriberId = telephonyManager.getSubscriberId(); //MissingPermission
   } catch (SecurityException e) {
     subscriberId = null;
   }

} else {
   subscriberId = null;
}

Get NetworkStats for an app:

long receivedBytes = 0;
long transmittedBytes = 0;

for (int networkType : networkTypes) {
    NetworkStats networkStats;
    try {
        networkStats = networkStatsManager
                .queryDetailsForUid(networkType,
                        subscriberId,
                        0,
                        System.currentTimeMillis(),
                        appUid);
    } catch (SecurityException e) {
        networkStats = null;
    }

    if(networkStats != null) {
        NetworkStats.Bucket bucketOut = new NetworkStats.Bucket();
        while (networkStats.hasNextBucket()) {
            networkStats.getNextBucket(bucketOut);
            long rxBytes = bucketOut.getRxBytes();
            long txBytes = bucketOut.getTxBytes();
            if (rxBytes >= 0) {
                receivedBytes += rxBytes;
            }
            if (txBytes >= 0) {
                transmittedBytes += txBytes;
            }
        }
        networkStats.close();
    }
}

Upvotes: 2

Dustin
Dustin

Reputation: 2154

We are using NetworkStatsManager.querySummaryForDevice(). Due to a serendipitous bug, we were passing null as the subscriberId for MOBILE in Q. It appears to be working on our devices. I'm not sure if this is a bug or a feature, but the values match our expected cellular usage.

All the said, we could just use TrafficStats for this use case, but it's erratic before Pie.

Upvotes: 0

Nandan Desai
Nandan Desai

Reputation: 397

The google team in the comment of the thread that you have mentioned has said: " Status: Won't Fix (Intended Behavior) This is Working As Intended. IMEI is a personal identifier and this is not given out to apps as a matter of policy. There is no workaround.". So I guess the methods in the class NetworkStatsManager which require IMSI (which is also considered as a personal identifier) to work (like the queryDetailsForUid(int, String, long, long, int)) are now broken in Android Q. You may use those methods to get Wifi usage details of the apps (by passing empty string for subscriberId) but for getting Mobile usage details, you now have to rely on the good old TrafficStats class until the issue gets noticed and fixed.

Upvotes: 1

Related Questions