Getting mobile data usage history using NetworkStatsManager
Asked Answered
A

2

17

I want to know the data usage history and noticed the "new" android-6 NetworkStatsManager which seems positive (I've used TrafficStats a while but that won't cover anything previous a reboot).

From the API documentation:

NOTE: This API requires the permission PACKAGE_USAGE_STATS, which is a system-level permission and will not be granted to third-party apps. However, declaring the permission implies intention to use the API and the user of the device can grant permission through the Settings application. Profile owner apps are automatically granted permission to query data on the profile they manage (that is, for any query except querySummaryForDevice(int, String, long, long)). Device owner apps likewise get access to usage data of the primary user.

I want to know the data usage on a aggregated level and not down to which app that uses the data so I tried to use it like this:

NetworkStatsManager service = context.getSystemService(NetworkStatsManager.class);

NetworkStats.Bucket bucket = 
        service.querySummaryForDevice(ConnectivityManager.TYPE_MOBILE, null, from, to);
...

Unfortunately that throws a SecurityException:

java.lang.SecurityException: NetworkStats: Neither user 10174 nor current process has android.permission.READ_NETWORK_USAGE_HISTORY.
at android.os.Parcel.readException(Parcel.java:1620)
at android.os.Parcel.readException(Parcel.java:1573)
at android.net.INetworkStatsSession$Stub$Proxy.getDeviceSummaryForNetwork(INetworkStatsSession.java:259)
at android.app.usage.NetworkStats.getDeviceSummaryForNetwork(NetworkStats.java:316)
at android.app.usage.NetworkStatsManager.querySummaryForDevice(NetworkStatsManager.java:100)
...

The android.permission.READ_NETWORK_USAGE_HISTORY permission is not allowed for third party apps. So this seemed like a dead end.

However, I drilled down a bit into the internals and found out that you can use the internal/hidden API to do the same thing without requesting any permissions:

INetworkStatsService service =
        INetworkStatsService.Stub.asInterface(
                ServiceManager.getService(Context.NETWORK_STATS_SERVICE));

INetworkStatsSession session = service.openSession();

NetworkTemplate mobileTemplate = NetworkTemplate.buildTemplateMobileWildcard();
int fields = NetworkStatsHistory.FIELD_RX_BYTES | NetworkStatsHistory.FIELD_TX_BYTES;

NetworkStatsHistory mobileHistory = session.getHistoryForNetwork(mobileTemplate, fields);

for (int i = 0; i < mobileHistory.size(); i++) {
    NetworkStatsHistory.Entry entry = mobileHistory.getValues(i, null);
    ...
}

session.close();

I really want to do the same with the public API, so, how do I do just that?

Aeolus answered 18/4, 2016 at 19:22 Comment(4)
Hi, I'm trying to do the same thing. For this permission: android.permission.READ_NETWORK_USAGE_HISTORY I remove warning on manifest and the app finally installed on my device. But when I try to get TYPE_MOBILE it return always 0. WIFI works fine. Have you resolved this issue? please let me know :)Shamanism
i also getting always 0Cinder
@Cinder Have you been able to fix this? I am having same issue with mobile data in android 8 devices (not all devices, but some)Solothurn
Second parameter of querySummaryForDevice shouldn't be null; use telephonyManager.getSubscriberId() insteadExtremadura
W
23

There is way to obtain the access to NetworkStateManager without getting access to private API. Here are the steps:

  1. Declare the required permissions in AndroidManifest.xml:

    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
    <uses-permission
        android:name="android.permission.PACKAGE_USAGE_STATS"
        tools:ignore="ProtectedPermissions"/>
    
    1. Ask for permission in Activity

    android.permission.PACKAGE_USAGE_STATS is not a normal permission, there cannot be simply requested. In order to check, whether the permission has been granted, check:

    AppOpsManager appOps = (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE);
    int mode = appOps.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS,
            android.os.Process.myUid(), getPackageName());
    if (mode == AppOpsManager.MODE_ALLOWED) {
        return true;
    }
    

    To ask for this permission, simply call Intent:

    Intent intent = new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS);
    startActivity(intent);
    

    Another permmission is also needed: Manifest.permission.READ_PHONE_STATE. It is needed only, when you need to get mobile data statistics. However, this is normal permission so can be requested as any other permission

    1. Use NetworkStatsManager:

Made a sample Github repo demonstrating the usage.

Wildon answered 9/9, 2016 at 12:58 Comment(2)
I'm stuck with READ_NETWORK_USAGE_HISTORY. tried out the repo on a 6.0.1 and got strange number from the NetworkStatsManager side. Are you sure this hack is still working? thanks a ton!Kaliski
getSubscriberId() is unfortunately crashing with java.lang.SecurityException: getSubscriberId: The user 10435 does not meet the requirements to access device identifiers in Android 11Antihalation
H
1
/*
getting youtube usage for both mobile and wifi.
 */
public long getYoutubeTotalusage(Context context) {
    String subId = getSubscriberId(context, ConnectivityManager.TYPE_MOBILE);
    return getYoutubeUsage(ConnectivityManager.TYPE_MOBILE, subId) + getYoutubeUsage(ConnectivityManager.TYPE_WIFI, "");
}


private long getYoutubeUsage(int networkType, String subScriberId) {
    NetworkStats networkStatsByApp;
    long currentYoutubeUsage = 0L;
    try {
        networkStatsByApp = networkStatsManager.querySummary(networkType, subScriberId, 0, System.currentTimeMillis());
        do {
            NetworkStats.Bucket bucket = new NetworkStats.Bucket();
            networkStatsByApp.getNextBucket(bucket);
            if (bucket.getUid() == packageUid) {
                //rajeesh : in some devices this is immediately looping twice and the second iteration is returning correct value. So result returning is moved to the end.
                currentYoutubeUsage = (bucket.getRxBytes() + bucket.getTxBytes());
            }
        } while (networkStatsByApp.hasNextBucket());

    } catch (RemoteException e) {
        e.printStackTrace();
    }

    return currentYoutubeUsage;
}


private String getSubscriberId(Context context, int networkType) {
    if (ConnectivityManager.TYPE_MOBILE == networkType) {
        TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
        return tm.getSubscriberId();
    }
    return "";
}
Hallucinosis answered 17/1, 2017 at 11:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.