How to get network usage of apps on Android Q?

2020-06-04 08:13发布

问题:

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:

  • I am getting IMEI null in Android Q?
  • https://issuetracker.google.com/issues/130202003
  • https://issuetracker.google.com/issues/129583175
  • https://developer.android.com/preview/privacy/data-identifiers#device-ids

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.

回答1:

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.



回答2:

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.