Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Tracking data usage of multiple App Widgets separately

Tags:

android

In my app, multiple App Widgets can be placed on the home screen, with each widget fetching remote data on a periodic basis.

My goals are:

  • track data usage associated with each of multiple widgets separately
  • WiFi and mobile data usage
  • of my own app only (not interested in data usage of other apps)
  • no extra permissions
  • allow widgets to perform network operations concurrently without double counting of data usage
  • include data usage in WebView activity initiated from widget
  • report results in-app (not just when tethered to Network Traffic Tool)

It's easy enough to log data usage for your own UID before and after a widget update with TrafficStats, by using getUidRxBytes() and getUidTxBytes() (passing your own app's UID), and then taking the difference. Whilst this rough-and-ready approach gives sensible values when the widgets run in series, when they are running concurrently there is inevitably a degree of double (triple etc) counting going on.

I'd rather not force the widgets to update in series, because I'd rather let the job scheduling engine of the device to work out what is best. So I need a way to separate data usage for each widget from each other.

It seems that the use of tags might be the way to go... tag network operations with the appWidgetId and then filter the stats based on this appWidgetId. I'm not convinced this approach will actually enable data usage of one widget to be distinguished from another (maybe the buckets will become confused), but the fact is I can't get tagging to work at all so I can't yet tell.

You can set the tag with setThreadStatsTag(tag) before a network operation starts, but I can't see any method in the TrafficStats class for retrieving results filtered by tag. Does anyone know any different?

So I then looked at NetworkStatsManager, and in particular NetworkStatsManager.queryDetailsForUidTag(... , tag). This seems to allow you to filter based on a specific tag, and works without any extra permissions (for your own app's UID) which is good. But I can't get that method to return any buckets with data, despite setting TrafficStats.setThreadStatsTag(appWidgetId) before network operations using HttpURLConnection, and querying with the same tag afterwards by calling NetworkStatsManager.queryDetailsForUidTag(... , appWidgetId).

On the other hand, NetworkStatsManager.queryDetailsForUid() (no tag parameter) does return buckets with valid data, so it seems to be just the tagging that isn't working, rather than anything fundamental I'm doing wrong with NetworkStatsManager per se.

I set the tag with:

@Override
protected void onPreExecute() {
    TrafficStats.setThreadStatsTag(appWidgetId);
}

@Override
protected Bitmap doInBackground(String... baseUrls) {
    // fetch data with HttpURLConnection
}

@Override
protected void onPostExecute(Bitmap bmp) {
    TrafficStats.clearThreadStatsTag();
}

And (for WiFi data) I query the stats with:

void networkStatsStuff() {
    NetworkStats networkStats;
    NetworkStats networkStatsTagged;
    int networkType = ConnectivityManager.TYPE_WIFI;

    try {
        networkStats = networkStatsManager.queryDetailsForUid(networkType, "", startTime, endTime, myUid);
    } catch (RemoteException e) {
        return;
    }

    networkStatsTagged = networkStatsManager.queryDetailsForUidTag(networkType, "", startTime, endTime, myUid, appWidgetId);

    long totalData = getTotalData(networkStats);
    long totalDataTagged = getTotalData(networkStatsTagged);

    Log.d(TAG, "totalData: " + totalData);             // this seems to work
    Log.d(TAG, "totalDataTagged: " + totalDataTagged); // this is always zero
}

long getTotalData(NetworkStats networkStats) {
    long totalData = 0;
    do {
        NetworkStats.Bucket bucket = new NetworkStats.Bucket();
        networkStats.getNextBucket(bucket);
        totalData = totalData + bucket.getRxBytes() + bucket.getTxBytes();

    } while (networkStats.hasNextBucket());
    return totalData;
}

What am I doing wrong? (EDIT: partial answer is below.) Or maybe there is a completely different way to achieve my aims?

like image 746
drmrbrewer Avatar asked Aug 12 '17 10:08

drmrbrewer


1 Answers

Partial answer:

So I think I was led astray by this guide, which is where I got the placement of setThreadStatsTag() and clearThreadStatsTag(), in preExecute() and postExecute() respectively of the AsyncTask.

When I move the setting and unsetting of the tag into the doInBackground() part of the AsyncTask, I then start seeing the tags... I noticed this first in the Android Device Monitor, but they're also there when using the NetworkStats methods that filter by tag.

So it seems more correct to use something like described in this guide, along the lines of:

@Override
protected Bitmap doInBackground(String... baseUrls) {
    TrafficStats.setThreadStatsTag(appWidgetId);
    try {
        // fetch data with HttpURLConnection
    } finally {
        TrafficStats.clearThreadStatsTag();
    }
}

So is the first guidance just plain wrong?

Still unresolved:

  • querying mobile data usage without any additional permissions (I fear that READ_PHONE_STATE permission is required... seems like overkill, and annoying considering that the permission is not required with the TrafficStats methods that also read mobile data usage, albeit along with other interfaces)
  • include data usage over WebView (again, I fear that this is not possible, i.e. not possible to tag traffic in a WebView and hence not possible to distinguish by tag just because of the nature of the protocols concerned... this and this may be relevant)
like image 154
drmrbrewer Avatar answered Sep 22 '22 13:09

drmrbrewer