Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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:

  • 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.

like image 374
android developer Avatar asked Jun 15 '19 14:06

android developer


1 Answers

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();
    }
}
like image 178
Atakan Yildirim Avatar answered Oct 22 '22 14:10

Atakan Yildirim