9

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.

android developer
  • 114,585
  • 152
  • 739
  • 1,270
  • Starting in Android Q, apps must have the READ_PRIVILEGED_PHONE_STATE privileged permission in order to access the device's non-resettable identifiers, which include both IMEI and serial number. Did you tried to add thit permission? – TiyebM Jun 24 '19 at 12:57
  • Yes, but it's not for normal apps. It's only for system apps. Others already asked about it. Example: https://stackoverflow.com/a/51987160/878126 . See here too: https://developer.android.com/preview/privacy/data-identifiers#device-ids – android developer Jun 24 '19 at 14:53

3 Answers3

2

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();
    }
}
Atakan Yildirim
  • 684
  • 11
  • 22
  • Seems to work well. From which Android version do you need PACKAGE_USAGE_STATS for this operation ? For all versions? I don't remember... And why do you have android:maxSdkVersion="28" ? Not needed anymore? And does it work with Wifi too, and only of the specified app? – android developer Nov 07 '21 at 16:28
  • @androiddeveloper 1- I'm not sure for this operation, but some NetworkStats operations require PACKAGE_USAGE_STATS permission. If it works without permission you can delete it. 2-And yes, android:maxSdkVersion="28" because we don't need this permission when we don't use TelephonyManager. (This is valid for this code, if you are going to use READ_PHONE_STATE permission for other functions, you can delete maxSdk tag). 3- I don't quite understand the last question but it works for Wifi and Mobile Data. – Atakan Yildirim Nov 08 '21 at 10:07
  • 1. Which operations here need it? 2. ok. 3. I mean that you can query about a specific app, of how many bytes it used. – android developer Nov 23 '21 at 08:30
  • 1
    Hello, please see this article for the permissions required: https://developer.android.com/reference/android/app/usage/NetworkStatsManager#history-queries. To check app usage, replace "appUid" with the app uid you want to query. – Atakan Yildirim Dec 04 '21 at 09:47
  • Oh ok. Thanks. Isn't it similar to what I wrote, though? I don't remember this question, but it seems like the code is very similar. I wonder why I didn't succeed with this... – android developer Dec 04 '21 at 10:39
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.

Nandan Desai
  • 397
  • 3
  • 10
  • The link is about IMEI. I talked about what's needed for `queryDetailsForUid` , which is `getSubscriberId` . But, even if it's about the same, is there maybe a solution if I have root? I tried to grant this permission either way, but the app isn't granted with it. Seems like it's really only for system apps. Using `TrafficStats ` is global, as I wrote. – android developer Jun 28 '19 at 07:20
  • I got confused between IMEI and IMSI. I have edited the answer. For now, I don't think there is a way access IMSI or IMEI number for Android Q other than for system apps. Although TrafficStats gives the global mobile usage, I think it is currently the only way close to what you want to achieve in Android Q. – Nandan Desai Jun 28 '19 at 10:02
  • According to this (https://developer.android.com/preview/privacy/data-identifiers#proc-net-filesystem), device's network state is stored in /proc/net. Try accessing that with root. – Nandan Desai Jun 28 '19 at 16:55
  • Seems it has a lot of files, but what to look for there? See: https://ibb.co/7QqBGfG – android developer Jun 29 '19 at 06:27
  • @androiddeveloper able to get IMSI in android Q? – NitZRobotKoder Jul 01 '19 at 07:43
  • @NitZRobotKoder Well, I got this one, if you are interested: https://stackoverflow.com/questions/29353640/how-do-i-get-meid-and-imei-information-using-adb-commands-on-android-5-0/29357619#29357619 , but it's only for rooted devices. I even updated the question to have it at the bottom... But the question here still remains, of how to do it officially. With or without IMEI/IMSI... – android developer Jul 01 '19 at 08:37
0

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.

Dustin
  • 2,064
  • 1
  • 16
  • 12